diff --git a/README.md b/README.md
index d2d55073b4..3a3b7f1d78 100644
--- a/README.md
+++ b/README.md
@@ -161,7 +161,7 @@ To *update* docker-compose with newest image (if you have started the docker-com
 docker-compose -f ./docker/docker-compose.yml pull && docker-compose -f ./docker/docker-compose.yml up
 ```
 
-The server will be running at `http://your-ip:2283/api` through `Nginx`
+The server will be running at `http://your-ip:2283/api`
 
 ## Step 3: Register User
 
@@ -225,6 +225,15 @@ make dev # required Makefile installed on the system.
 
 All servers and web container are hot reload for quick feedback loop.
 
+## Note for developers
+### 1 - OpenAPI
+OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). When you add a new or modify an existing endpoint, you must run the generate command below to update the client SDK.
+
+```bash
+npm run api:generate # Run from server directory
+```
+You can find the generated client SDK in the [`web/src/api`](web/src/api) for Typescript SDK and [`mobile/openapi`](mobile/openapi) for Dart SDK.
+
 # Support
 
 If you like the app, find it helpful, and want to support me to offset the cost of publishing to AppStores, you can sponsor the project with [**Github Sponsor**](https://github.com/sponsors/alextran1502), or a one time donation with the Buy Me a coffee link below.
diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml
index ecf2f65ca3..b570c2b2e9 100644
--- a/mobile/analysis_options.yaml
+++ b/mobile/analysis_options.yaml
@@ -21,10 +21,18 @@ linter:
   # or a specific dart file by using the `// ignore: name_of_lint` and
   # `// ignore_for_file: name_of_lint` syntax on the line or in the file
   # producing the lint.
+
   rules:
     # avoid_print: false  # Uncomment to disable the `avoid_print` rule
-    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+    # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
     use_build_context_synchronously: false
+    require_trailing_commas: true
+    unrelated_type_equality_checks: true
 
 # Additional information about this file can be found at
 # https://dart.dev/guides/language/analysis-options
+analyzer:
+  exclude:
+    - openapi/
+    - openapi/test/
+    - lib/generated_plugin_registrant.dart
diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json
index 7d568f60fb..4a239b09fa 100644
--- a/mobile/assets/i18n/en-US.json
+++ b/mobile/assets/i18n/en-US.json
@@ -71,6 +71,7 @@
   "login_form_label_password": "Password",
   "login_form_password_hint": "password",
   "login_form_save_login": "Stay logged in",
+  "login_form_failed_login": "Error logging you in, check server url, email and password",
   "monthly_title_text_date_format": "MMMM y",
   "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
   "profile_drawer_sign_out": "Sign Out",
@@ -81,6 +82,7 @@
   "search_result_page_new_search_hint": "New Search",
   "select_additional_user_for_sharing_page_suggestions": "Suggestions",
   "select_user_for_sharing_page_err_album": "Failed to create album",
+  "select_user_for_sharing_page_share_suggestions": "Suggestions",
   "share_add": "Add",
   "share_add_photos": "Add photos",
   "share_add_title": "Add a title",
@@ -100,4 +102,4 @@
   "version_announcement_overlay_text_2": "please take your time to visit the ",
   "version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
   "version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
-}
\ No newline at end of file
+}
diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart
index 5e0e51a77d..c14f2f39f6 100644
--- a/mobile/lib/main.dart
+++ b/mobile/lib/main.dart
@@ -46,12 +46,15 @@ void main() async {
     Locale('de', 'DE')
   ];
 
-  runApp(EasyLocalization(
+  runApp(
+    EasyLocalization(
       supportedLocales: locales,
       path: 'assets/i18n',
       useFallbackTranslations: true,
       fallbackLocale: locales.first,
-      child: const ProviderScope(child: ImmichApp())));
+      child: const ProviderScope(child: ImmichApp()),
+    ),
+  );
 }
 
 class ImmichApp extends ConsumerStatefulWidget {
@@ -111,6 +114,7 @@ class ImmichAppState extends ConsumerState<ImmichApp>
   @override
   initState() {
     super.initState();
+
     initApp().then((_) => debugPrint("App Init Completed"));
   }
 
@@ -120,10 +124,9 @@ class ImmichAppState extends ConsumerState<ImmichApp>
     super.dispose();
   }
 
-  final _immichRouter = AppRouter();
-
   @override
   Widget build(BuildContext context) {
+    var router = ref.watch(appRouterProvider);
     ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
 
     return MaterialApp(
@@ -142,7 +145,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>
               primarySwatch: Colors.indigo,
               fontFamily: 'WorkSans',
               snackBarTheme: const SnackBarThemeData(
-                  contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
+                contentTextStyle: TextStyle(fontFamily: 'WorkSans'),
+              ),
               scaffoldBackgroundColor: immichBackgroundColor,
               appBarTheme: const AppBarTheme(
                 backgroundColor: immichBackgroundColor,
@@ -152,9 +156,10 @@ class ImmichAppState extends ConsumerState<ImmichApp>
                 systemOverlayStyle: SystemUiOverlayStyle.dark,
               ),
             ),
-            routeInformationParser: _immichRouter.defaultRouteParser(),
-            routerDelegate: _immichRouter.delegate(
-                navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
+            routeInformationParser: router.defaultRouteParser(),
+            routerDelegate: router.delegate(
+              navigatorObservers: () => [TabNavigationObserver(ref: ref)],
+            ),
           ),
           const ImmichLoadingOverlay(),
           const VersionAnnouncementOverlay(),
diff --git a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart
index a9e12b848e..4bed6eb5f1 100644
--- a/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart
+++ b/mobile/lib/modules/asset_viewer/providers/image_viewer_page_state.provider.dart
@@ -3,17 +3,20 @@ import 'package:fluttertoast/fluttertoast.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
 import 'package:immich_mobile/modules/asset_viewer/services/image_viewer.service.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:immich_mobile/shared/ui/immich_toast.dart';
+import 'package:openapi/api.dart';
 
 class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
-  final ImageViewerService _imageViewerService = ImageViewerService();
+  final ImageViewerService _imageViewerService;
 
-  ImageViewerStateNotifier()
-      : super(ImageViewerPageState(
-            downloadAssetStatus: DownloadAssetStatus.idle));
+  ImageViewerStateNotifier(this._imageViewerService)
+      : super(
+          ImageViewerPageState(
+            downloadAssetStatus: DownloadAssetStatus.idle,
+          ),
+        );
 
-  void downloadAsset(ImmichAsset asset, BuildContext context) async {
+  void downloadAsset(AssetResponseDto asset, BuildContext context) async {
     state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.loading);
 
     bool isSuccess = await _imageViewerService.downloadAssetToDevice(asset);
@@ -43,4 +46,5 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
 
 final imageViewerStateProvider =
     StateNotifierProvider<ImageViewerStateNotifier, ImageViewerPageState>(
-        ((ref) => ImageViewerStateNotifier()));
+  ((ref) => ImageViewerStateNotifier(ref.watch(imageViewerServiceProvider))),
+);
diff --git a/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart b/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart
index 9ee9e8a67a..771b0873d8 100644
--- a/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart
+++ b/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart
@@ -1,33 +1,35 @@
 import 'dart:io';
 
 import 'package:flutter/foundation.dart';
-import 'package:hive_flutter/hive_flutter.dart';
-import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
+import 'package:openapi/api.dart';
 import 'package:path/path.dart' as p;
-import 'package:http/http.dart' as http;
 
 import 'package:photo_manager/photo_manager.dart';
 import 'package:path_provider/path_provider.dart';
 
+final imageViewerServiceProvider =
+    Provider((ref) => ImageViewerService(ref.watch(apiServiceProvider)));
+
 class ImageViewerService {
-  Future<bool> downloadAssetToDevice(ImmichAsset asset) async {
+  final ApiService _apiService;
+  ImageViewerService(this._apiService);
+
+  Future<bool> downloadAssetToDevice(AssetResponseDto asset) async {
     try {
       String fileName = p.basename(asset.originalPath);
-      var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
-      Uri filePath = Uri.parse(
-          "$savedEndpoint/asset/download?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false");
 
-      var res = await http.get(
-        filePath,
-        headers: {
-          "Authorization": "Bearer ${Hive.box(userInfoBox).get(accessTokenKey)}"
-        },
+      var res = await _apiService.assetApi.downloadFileWithHttpInfo(
+        asset.deviceAssetId,
+        asset.deviceId,
+        isThumb: false,
+        isWeb: false,
       );
 
       final AssetEntity? entity;
 
-      if (asset.type == 'IMAGE') {
+      if (asset.type == AssetTypeEnum.IMAGE) {
         entity = await PhotoManager.editor.saveImage(
           res.bodyBytes,
           title: p.basename(asset.originalPath),
diff --git a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart
index 892ef04c88..952be9448e 100644
--- a/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart
+++ b/mobile/lib/modules/asset_viewer/ui/exif_bottom_sheet.dart
@@ -2,13 +2,12 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_map/flutter_map.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
-import 'package:intl/intl.dart';
+import 'package:openapi/api.dart';
 import 'package:path/path.dart' as p;
 import 'package:latlong2/latlong.dart';
 
 class ExifBottomSheet extends ConsumerWidget {
-  final ImmichAssetWithExif assetDetail;
+  final AssetResponseDto assetDetail;
 
   const ExifBottomSheet({Key? key, required this.assetDetail})
       : super(key: key);
@@ -26,8 +25,10 @@ class ExifBottomSheet extends ConsumerWidget {
           ),
           child: FlutterMap(
             options: MapOptions(
-              center: LatLng(assetDetail.exifInfo!.latitude!,
-                  assetDetail.exifInfo!.longitude!),
+              center: LatLng(
+                assetDetail.exifInfo?.latitude?.toDouble() ?? 0,
+                assetDetail.exifInfo?.longitude?.toDouble() ?? 0,
+              ),
               zoom: 16.0,
             ),
             layers: [
@@ -46,10 +47,13 @@ class ExifBottomSheet extends ConsumerWidget {
                 markers: [
                   Marker(
                     anchorPos: AnchorPos.align(AnchorAlign.top),
-                    point: LatLng(assetDetail.exifInfo!.latitude!,
-                        assetDetail.exifInfo!.longitude!),
+                    point: LatLng(
+                      assetDetail.exifInfo?.latitude?.toDouble() ?? 0,
+                      assetDetail.exifInfo?.longitude?.toDouble() ?? 0,
+                    ),
                     builder: (ctx) => const Image(
-                        image: AssetImage('assets/location-pin.png')),
+                      image: AssetImage('assets/location-pin.png'),
+                    ),
                   ),
                 ],
               ),
@@ -63,7 +67,10 @@ class ExifBottomSheet extends ConsumerWidget {
       return Text(
         "${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}",
         style: TextStyle(
-            fontSize: 12, color: Colors.grey[200], fontWeight: FontWeight.bold),
+          fontSize: 12,
+          color: Colors.grey[200],
+          fontWeight: FontWeight.bold,
+        ),
       );
     }
 
@@ -74,7 +81,7 @@ class ExifBottomSheet extends ConsumerWidget {
           if (assetDetail.exifInfo?.dateTimeOriginal != null)
             Text(
               DateFormat('date_format'.tr()).format(
-                DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!),
+                assetDetail.exifInfo!.dateTimeOriginal!,
               ),
               style: TextStyle(
                 color: Colors.grey[400],
@@ -151,7 +158,8 @@ class ExifBottomSheet extends ConsumerWidget {
                     ),
                     subtitle: assetDetail.exifInfo?.exifImageHeight != null
                         ? Text(
-                            "${assetDetail.exifInfo?.exifImageHeight} x ${assetDetail.exifInfo?.exifImageWidth}  ${assetDetail.exifInfo?.fileSizeInByte!}B ")
+                            "${assetDetail.exifInfo?.exifImageHeight} x ${assetDetail.exifInfo?.exifImageWidth}  ${assetDetail.exifInfo?.fileSizeInByte!}B ",
+                          )
                         : null,
                   ),
                   if (assetDetail.exifInfo?.make != null)
@@ -166,7 +174,8 @@ class ExifBottomSheet extends ConsumerWidget {
                         style: const TextStyle(fontWeight: FontWeight.bold),
                       ),
                       subtitle: Text(
-                          "ƒ/${assetDetail.exifInfo?.fNumber}   1/${(1 / (assetDetail.exifInfo?.exposureTime ?? 1)).toStringAsFixed(0)}   ${assetDetail.exifInfo?.focalLength}mm   ISO${assetDetail.exifInfo?.iso} "),
+                        "ƒ/${assetDetail.exifInfo?.fNumber}   1/${(1 / (assetDetail.exifInfo?.exposureTime ?? 1)).toStringAsFixed(0)}   ${assetDetail.exifInfo?.focalLength}mm   ISO${assetDetail.exifInfo?.iso} ",
+                      ),
                     ),
                 ],
               ),
diff --git a/mobile/lib/modules/asset_viewer/ui/remote_photo_view.dart b/mobile/lib/modules/asset_viewer/ui/remote_photo_view.dart
index 3a9bce99be..384eae20c4 100644
--- a/mobile/lib/modules/asset_viewer/ui/remote_photo_view.dart
+++ b/mobile/lib/modules/asset_viewer/ui/remote_photo_view.dart
@@ -17,16 +17,20 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
     bool allowMoving = _status == _RemoteImageStatus.full;
 
     return PhotoView(
-        imageProvider: _imageProvider,
-        minScale: PhotoViewComputedScale.contained,
-        maxScale: allowMoving ? 1.0 : PhotoViewComputedScale.contained,
-        enablePanAlways: true,
-        scaleStateChangedCallback: _scaleStateChanged,
-        onScaleEnd: _onScaleListener);
+      imageProvider: _imageProvider,
+      minScale: PhotoViewComputedScale.contained,
+      maxScale: allowMoving ? 1.0 : PhotoViewComputedScale.contained,
+      enablePanAlways: true,
+      scaleStateChangedCallback: _scaleStateChanged,
+      onScaleEnd: _onScaleListener,
+    );
   }
 
-  void _onScaleListener(BuildContext context, ScaleEndDetails details,
-      PhotoViewControllerValue controllerValue) {
+  void _onScaleListener(
+    BuildContext context,
+    ScaleEndDetails details,
+    PhotoViewControllerValue controllerValue,
+  ) {
     // Disable swipe events when zoomed in
     if (_zoomedIn) return;
 
@@ -42,12 +46,17 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
   }
 
   CachedNetworkImageProvider _authorizedImageProvider(String url) {
-    return CachedNetworkImageProvider(url,
-        headers: {"Authorization": widget.authToken}, cacheKey: url);
+    return CachedNetworkImageProvider(
+      url,
+      headers: {"Authorization": widget.authToken},
+      cacheKey: url,
+    );
   }
 
   void _performStateTransition(
-      _RemoteImageStatus newStatus, CachedNetworkImageProvider provider) {
+    _RemoteImageStatus newStatus,
+    CachedNetworkImageProvider provider,
+  ) {
     // Transition to same status is forbidden
     if (_status == newStatus) return;
     // Transition full -> thumbnail is forbidden
@@ -67,19 +76,22 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
         _authorizedImageProvider(widget.thumbnailUrl);
     _imageProvider = thumbnailProvider;
 
-    thumbnailProvider
-        .resolve(const ImageConfiguration())
-        .addListener(ImageStreamListener((ImageInfo imageInfo, _) {
-      _performStateTransition(_RemoteImageStatus.thumbnail, thumbnailProvider);
-    }));
+    thumbnailProvider.resolve(const ImageConfiguration()).addListener(
+      ImageStreamListener((ImageInfo imageInfo, _) {
+        _performStateTransition(
+          _RemoteImageStatus.thumbnail,
+          thumbnailProvider,
+        );
+      }),
+    );
 
     CachedNetworkImageProvider fullProvider =
         _authorizedImageProvider(widget.imageUrl);
-    fullProvider
-        .resolve(const ImageConfiguration())
-        .addListener(ImageStreamListener((ImageInfo imageInfo, _) {
-      _performStateTransition(_RemoteImageStatus.full, fullProvider);
-    }));
+    fullProvider.resolve(const ImageConfiguration()).addListener(
+      ImageStreamListener((ImageInfo imageInfo, _) {
+        _performStateTransition(_RemoteImageStatus.full, fullProvider);
+      }),
+    );
   }
 
   @override
@@ -90,14 +102,14 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
 }
 
 class RemotePhotoView extends StatefulWidget {
-  const RemotePhotoView(
-      {Key? key,
-      required this.thumbnailUrl,
-      required this.imageUrl,
-      required this.authToken,
-      required this.onSwipeDown,
-      required this.onSwipeUp})
-      : super(key: key);
+  const RemotePhotoView({
+    Key? key,
+    required this.thumbnailUrl,
+    required this.imageUrl,
+    required this.authToken,
+    required this.onSwipeDown,
+    required this.onSwipeUp,
+  }) : super(key: key);
 
   final String thumbnailUrl;
   final String imageUrl;
diff --git a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart
index 39e8ecf6a2..eba66a2691 100644
--- a/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart
+++ b/mobile/lib/modules/asset_viewer/ui/top_control_app_bar.dart
@@ -3,17 +3,17 @@ import 'dart:developer';
 import 'package:auto_route/auto_route.dart';
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
-  const TopControlAppBar(
-      {Key? key,
-      required this.asset,
-      required this.onMoreInfoPressed,
-      required this.onDownloadPressed})
-      : super(key: key);
+  const TopControlAppBar({
+    Key? key,
+    required this.asset,
+    required this.onMoreInfoPressed,
+    required this.onDownloadPressed,
+  }) : super(key: key);
 
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
   final Function onMoreInfoPressed;
   final Function onDownloadPressed;
 
@@ -54,12 +54,13 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
               : const Icon(Icons.favorite_border_rounded),
         ),
         IconButton(
-            iconSize: iconSize,
-            splashRadius: iconSize,
-            onPressed: () {
-              onMoreInfoPressed();
-            },
-            icon: const Icon(Icons.more_horiz_rounded))
+          iconSize: iconSize,
+          splashRadius: iconSize,
+          onPressed: () {
+            onMoreInfoPressed();
+          },
+          icon: const Icon(Icons.more_horiz_rounded),
+        )
       ],
     );
   }
diff --git a/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart b/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart
index a59fc825ce..d88b8a9dcf 100644
--- a/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart
+++ b/mobile/lib/modules/asset_viewer/views/image_viewer_page.dart
@@ -11,17 +11,16 @@ import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
 import 'package:immich_mobile/modules/asset_viewer/ui/remote_photo_view.dart';
 import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
 import 'package:immich_mobile/modules/home/services/asset.service.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
+import 'package:openapi/api.dart';
 
 // ignore: must_be_immutable
 class ImageViewerPage extends HookConsumerWidget {
   final String imageUrl;
   final String heroTag;
   final String thumbnailUrl;
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
 
-  ImmichAssetWithExif? assetDetail;
+  AssetResponseDto? assetDetail;
 
   ImageViewerPage({
     Key? key,
@@ -54,10 +53,13 @@ class ImageViewerPage extends HookConsumerWidget {
       );
     }
 
-    useEffect(() {
-      getAssetExif();
-      return null;
-    }, []);
+    useEffect(
+      () {
+        getAssetExif();
+        return null;
+      },
+      [],
+    );
 
     return Scaffold(
       backgroundColor: Colors.black,
@@ -75,14 +77,15 @@ class ImageViewerPage extends HookConsumerWidget {
           children: [
             Center(
               child: Hero(
-                  tag: heroTag,
-                  child: RemotePhotoView(
-                    thumbnailUrl: thumbnailUrl,
-                    imageUrl: imageUrl,
-                    authToken: "Bearer ${box.get(accessTokenKey)}",
-                    onSwipeDown: () => AutoRouter.of(context).pop(),
-                    onSwipeUp: () => showInfo(),
-                  )),
+                tag: heroTag,
+                child: RemotePhotoView(
+                  thumbnailUrl: thumbnailUrl,
+                  imageUrl: imageUrl,
+                  authToken: "Bearer ${box.get(accessTokenKey)}",
+                  onSwipeDown: () => AutoRouter.of(context).pop(),
+                  onSwipeUp: () => showInfo(),
+                ),
+              ),
             ),
             if (downloadAssetStatus == DownloadAssetStatus.loading)
               const Center(
diff --git a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart
index 99be903dcb..4a2e4908ee 100644
--- a/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart
+++ b/mobile/lib/modules/asset_viewer/views/video_viewer_page.dart
@@ -12,15 +12,14 @@ import 'package:immich_mobile/modules/asset_viewer/ui/download_loading_indicator
 import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
 import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
 import 'package:immich_mobile/modules/home/services/asset.service.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
+import 'package:openapi/api.dart';
 import 'package:video_player/video_player.dart';
 
 // ignore: must_be_immutable
 class VideoViewerPage extends HookConsumerWidget {
   final String videoUrl;
-  final ImmichAsset asset;
-  ImmichAssetWithExif? assetDetail;
+  final AssetResponseDto asset;
+  AssetResponseDto? assetDetail;
 
   VideoViewerPage({Key? key, required this.videoUrl, required this.asset})
       : super(key: key);
@@ -49,10 +48,13 @@ class VideoViewerPage extends HookConsumerWidget {
           await ref.watch(assetServiceProvider).getAssetById(asset.id);
     }
 
-    useEffect(() {
-      getAssetExif();
-      return null;
-    }, []);
+    useEffect(
+      () {
+        getAssetExif();
+        return null;
+      },
+      [],
+    );
 
     return Scaffold(
       backgroundColor: Colors.black,
@@ -116,8 +118,10 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
 
   Future<void> initializePlayer() async {
     try {
-      videoPlayerController = VideoPlayerController.network(widget.url,
-          httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"});
+      videoPlayerController = VideoPlayerController.network(
+        widget.url,
+        httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"},
+      );
 
       await videoPlayerController.initialize();
       _createChewieController();
diff --git a/mobile/lib/modules/backup/models/backup_state.model.dart b/mobile/lib/modules/backup/models/backup_state.model.dart
index c3c66f08c8..8f41b0200a 100644
--- a/mobile/lib/modules/backup/models/backup_state.model.dart
+++ b/mobile/lib/modules/backup/models/backup_state.model.dart
@@ -1,10 +1,10 @@
 import 'package:cancellation_token_http/http.dart';
 import 'package:collection/collection.dart';
+import 'package:openapi/api.dart';
 import 'package:photo_manager/photo_manager.dart';
 
 import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
 import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
-import 'package:immich_mobile/shared/models/server_info.model.dart';
 
 enum BackUpProgressEnum { idle, inProgress, done }
 
@@ -14,7 +14,7 @@ class BackUpState {
   final List<String> allAssetsInDatabase;
   final double progressInPercentage;
   final CancellationToken cancelToken;
-  final ServerInfo serverInfo;
+  final ServerInfoResponseDto serverInfo;
 
   /// All available albums on the device
   final List<AvailableAlbum> availableAlbums;
@@ -49,7 +49,7 @@ class BackUpState {
     List<String>? allAssetsInDatabase,
     double? progressInPercentage,
     CancellationToken? cancelToken,
-    ServerInfo? serverInfo,
+    ServerInfoResponseDto? serverInfo,
     List<AvailableAlbum>? availableAlbums,
     Set<AssetPathEntity>? selectedBackupAlbums,
     Set<AssetPathEntity>? excludedBackupAlbums,
@@ -93,8 +93,10 @@ class BackUpState {
         collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
         collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
         collectionEquals(other.allUniqueAssets, allUniqueAssets) &&
-        collectionEquals(other.selectedAlbumsBackupAssetsIds,
-            selectedAlbumsBackupAssetsIds) &&
+        collectionEquals(
+          other.selectedAlbumsBackupAssetsIds,
+          selectedAlbumsBackupAssetsIds,
+        ) &&
         other.currentUploadAsset == currentUploadAsset;
   }
 
diff --git a/mobile/lib/modules/backup/models/check_duplicate_asset_response.model.dart b/mobile/lib/modules/backup/models/check_duplicate_asset_response.model.dart
deleted file mode 100644
index 2fc30ea61f..0000000000
--- a/mobile/lib/modules/backup/models/check_duplicate_asset_response.model.dart
+++ /dev/null
@@ -1,48 +0,0 @@
-import 'dart:convert';
-
-class CheckDuplicateAssetResponse {
-  final bool isExist;
-  CheckDuplicateAssetResponse({
-    required this.isExist,
-  });
-
-  CheckDuplicateAssetResponse copyWith({
-    bool? isExist,
-  }) {
-    return CheckDuplicateAssetResponse(
-      isExist: isExist ?? this.isExist,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll({'isExist': isExist});
-
-    return result;
-  }
-
-  factory CheckDuplicateAssetResponse.fromMap(Map<String, dynamic> map) {
-    return CheckDuplicateAssetResponse(
-      isExist: map['isExist'] ?? false,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory CheckDuplicateAssetResponse.fromJson(String source) =>
-      CheckDuplicateAssetResponse.fromMap(json.decode(source));
-
-  @override
-  String toString() => 'CheckDuplicateAssetResponse(isExist: $isExist)';
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is CheckDuplicateAssetResponse && other.isExist == isExist;
-  }
-
-  @override
-  int get hashCode => isExist.hashCode;
-}
diff --git a/mobile/lib/modules/backup/providers/backup.provider.dart b/mobile/lib/modules/backup/providers/backup.provider.dart
index 2f6970f55e..e71a8dbfd8 100644
--- a/mobile/lib/modules/backup/providers/backup.provider.dart
+++ b/mobile/lib/modules/backup/providers/backup.provider.dart
@@ -12,8 +12,8 @@ import 'package:immich_mobile/modules/backup/providers/error_backup_list.provide
 import 'package:immich_mobile/modules/backup/services/backup.service.dart';
 import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
-import 'package:immich_mobile/shared/models/server_info.model.dart';
 import 'package:immich_mobile/shared/services/server_info.service.dart';
+import 'package:openapi/api.dart';
 import 'package:photo_manager/photo_manager.dart';
 
 class BackupNotifier extends StateNotifier<BackUpState> {
@@ -28,12 +28,12 @@ class BackupNotifier extends StateNotifier<BackUpState> {
             allAssetsInDatabase: const [],
             progressInPercentage: 0,
             cancelToken: CancellationToken(),
-            serverInfo: ServerInfo(
+            serverInfo: ServerInfoResponseDto(
               diskAvailable: "0",
               diskAvailableRaw: 0,
               diskSize: "0",
               diskSizeRaw: 0,
-              diskUsagePercentage: 0.0,
+              diskUsagePercentage: 0,
               diskUse: "0",
               diskUseRaw: 0,
             ),
@@ -113,7 +113,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
     // Get all albums on the device
     List<AvailableAlbum> availableAlbums = [];
     List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
-        hasAll: true, type: RequestType.common);
+      hasAll: true,
+      type: RequestType.common,
+    );
 
     for (AssetPathEntity album in albums) {
       AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
@@ -156,7 +158,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
 
       // Get album that contains all assets
       var list = await PhotoManager.getAssetPathList(
-          hasAll: true, onlyAll: true, type: RequestType.common);
+        hasAll: true,
+        onlyAll: true,
+        type: RequestType.common,
+      );
       AssetPathEntity albumHasAllAssets = list.first;
 
       backupAlbumInfoBox.put(
@@ -175,13 +180,15 @@ class BackupNotifier extends StateNotifier<BackUpState> {
       for (var selectedAlbumId in backupAlbumInfo!.selectedAlbumIds) {
         var albumAsset = await AssetPathEntity.fromId(selectedAlbumId);
         state = state.copyWith(
-            selectedBackupAlbums: {...state.selectedBackupAlbums, albumAsset});
+          selectedBackupAlbums: {...state.selectedBackupAlbums, albumAsset},
+        );
       }
 
       for (var excludedAlbumId in backupAlbumInfo.excludedAlbumsIds) {
         var albumAsset = await AssetPathEntity.fromId(excludedAlbumId);
         state = state.copyWith(
-            excludedBackupAlbums: {...state.excludedBackupAlbums, albumAsset});
+          excludedBackupAlbums: {...state.excludedBackupAlbums, albumAsset},
+        );
       }
     } catch (e) {
       debugPrint("[ERROR] Failed to generate album from id $e");
@@ -211,8 +218,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
 
     Set<AssetEntity> allUniqueAssets =
         assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
-    List<String> allAssetsInDatabase =
-        await _backupService.getDeviceBackupAsset();
+    var allAssetsInDatabase = await _backupService.getDeviceBackupAsset();
+
+    if (allAssetsInDatabase == null) {
+      return;
+    }
 
     // Find asset that were backup from selected albums
     Set<String> selectedAlbumsBackupAssets =
@@ -328,23 +338,27 @@ class BackupNotifier extends StateNotifier<BackUpState> {
   void cancelBackup() {
     state.cancelToken.cancel();
     state = state.copyWith(
-        backupProgress: BackUpProgressEnum.idle, progressInPercentage: 0.0);
+      backupProgress: BackUpProgressEnum.idle,
+      progressInPercentage: 0.0,
+    );
   }
 
   void _onAssetUploaded(String deviceAssetId, String deviceId) {
-    state = state.copyWith(selectedAlbumsBackupAssetsIds: {
-      ...state.selectedAlbumsBackupAssetsIds,
-      deviceAssetId
-    }, allAssetsInDatabase: [
-      ...state.allAssetsInDatabase,
-      deviceAssetId
-    ]);
+    state = state.copyWith(
+      selectedAlbumsBackupAssetsIds: {
+        ...state.selectedAlbumsBackupAssetsIds,
+        deviceAssetId
+      },
+      allAssetsInDatabase: [...state.allAssetsInDatabase, deviceAssetId],
+    );
 
     if (state.allUniqueAssets.length -
             state.selectedAlbumsBackupAssetsIds.length ==
         0) {
       state = state.copyWith(
-          backupProgress: BackUpProgressEnum.done, progressInPercentage: 0.0);
+        backupProgress: BackUpProgressEnum.done,
+        progressInPercentage: 0.0,
+      );
     }
 
     _updateServerInfo();
@@ -352,24 +366,19 @@ class BackupNotifier extends StateNotifier<BackUpState> {
 
   void _onUploadProgress(int sent, int total) {
     state = state.copyWith(
-        progressInPercentage: (sent.toDouble() / total.toDouble() * 100));
+      progressInPercentage: (sent.toDouble() / total.toDouble() * 100),
+    );
   }
 
   Future<void> _updateServerInfo() async {
     var serverInfo = await _serverInfoService.getServerInfo();
 
     // Update server info
-    state = state.copyWith(
-      serverInfo: ServerInfo(
-        diskSize: serverInfo.diskSize,
-        diskUse: serverInfo.diskUse,
-        diskAvailable: serverInfo.diskAvailable,
-        diskSizeRaw: serverInfo.diskSizeRaw,
-        diskUseRaw: serverInfo.diskUseRaw,
-        diskAvailableRaw: serverInfo.diskAvailableRaw,
-        diskUsagePercentage: serverInfo.diskUsagePercentage,
-      ),
-    );
+    if (serverInfo != null) {
+      state = state.copyWith(
+        serverInfo: serverInfo,
+      );
+    }
   }
 
   void resumeBackup() {
diff --git a/mobile/lib/modules/backup/services/backup.service.dart b/mobile/lib/modules/backup/services/backup.service.dart
index c1b1576ab8..59696028f4 100644
--- a/mobile/lib/modules/backup/services/backup.service.dart
+++ b/mobile/lib/modules/backup/services/backup.service.dart
@@ -2,59 +2,38 @@ import 'dart:async';
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
 import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/modules/backup/models/check_duplicate_asset_response.model.dart';
 import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
 import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
-import 'package:immich_mobile/shared/models/device_info.model.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
 import 'package:immich_mobile/utils/files_helper.dart';
+import 'package:openapi/api.dart';
 import 'package:photo_manager/photo_manager.dart';
 import 'package:http_parser/http_parser.dart';
 import 'package:path/path.dart' as p;
 import 'package:cancellation_token_http/http.dart' as http;
 
-final backupServiceProvider =
-    Provider((ref) => BackupService(ref.watch(networkServiceProvider)));
+final backupServiceProvider = Provider(
+  (ref) => BackupService(
+    ref.watch(apiServiceProvider),
+  ),
+);
 
 class BackupService {
-  final NetworkService _networkService;
+  final ApiService _apiService;
+  BackupService(this._apiService);
 
-  BackupService(this._networkService);
-
-  Future<List<String>> getDeviceBackupAsset() async {
-    String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
-
-    Response response =
-        await _networkService.getRequest(url: "asset/$deviceId");
-    List<dynamic> result = jsonDecode(response.toString());
-
-    return result.cast<String>();
-  }
-
-  Future<bool> checkDuplicateAsset(String deviceAssetId) async {
+  Future<List<String>?> getDeviceBackupAsset() async {
     String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
 
     try {
-      Response response =
-          await _networkService.postRequest(url: "asset/check", data: {
-        "deviceId": deviceId,
-        "deviceAssetId": deviceAssetId,
-      });
-
-      if (response.statusCode == 200) {
-        var result = CheckDuplicateAssetResponse.fromJson(response.toString());
-
-        return result.isExist;
-      } else {
-        return false;
-      }
+      return await _apiService.assetApi.getUserAssetsByDeviceId(deviceId);
     } catch (e) {
-      return false;
+      debugPrint('Error [getDeviceBackupAsset] ${e.toString()}');
+      return null;
     }
   }
 
@@ -99,9 +78,11 @@ class BackupService {
           var box = Hive.box(userInfoBox);
 
           var req = MultipartRequest(
-              'POST', Uri.parse('$savedEndpoint/asset/upload'),
-              onProgress: ((bytes, totalBytes) =>
-                  uploadProgressCb(bytes, totalBytes)));
+            'POST',
+            Uri.parse('$savedEndpoint/asset/upload'),
+            onProgress: ((bytes, totalBytes) =>
+                uploadProgressCb(bytes, totalBytes)),
+          );
           req.headers["Authorization"] = "Bearer ${box.get(accessTokenKey)}";
 
           req.fields['deviceAssetId'] = entity.id;
@@ -133,16 +114,19 @@ class BackupService {
             var error = jsonDecode(data);
 
             debugPrint(
-                "Error(${error['statusCode']}) uploading ${entity.id} | $originalFileName | Created on ${entity.createDateTime} | ${error['error']}");
+              "Error(${error['statusCode']}) uploading ${entity.id} | $originalFileName | Created on ${entity.createDateTime} | ${error['error']}",
+            );
 
-            errorCb(ErrorUploadAsset(
-              asset: entity,
-              id: entity.id,
-              createdAt: entity.createDateTime,
-              fileName: originalFileName,
-              fileType: _getAssetType(entity.type),
-              errorMessage: error['error'],
-            ));
+            errorCb(
+              ErrorUploadAsset(
+                asset: entity,
+                id: entity.id,
+                createdAt: entity.createDateTime,
+                fileName: originalFileName,
+                fileType: _getAssetType(entity.type),
+                errorMessage: error['error'],
+              ),
+            );
             continue;
           }
         }
@@ -160,8 +144,6 @@ class BackupService {
     }
   }
 
-  void sendBackupRequest(AssetEntity entity) {}
-
   String _getAssetType(AssetType assetType) {
     switch (assetType) {
       case AssetType.audio:
@@ -175,15 +157,29 @@ class BackupService {
     }
   }
 
-  Future<DeviceInfoRemote> setAutoBackup(
-      bool status, String deviceId, String deviceType) async {
-    var res = await _networkService.patchRequest(url: 'device-info', data: {
-      "isAutoBackup": status,
-      "deviceId": deviceId,
-      "deviceType": deviceType,
-    });
+  Future<DeviceInfoResponseDto> setAutoBackup(
+    bool status,
+    String deviceId,
+    DeviceTypeEnum deviceType,
+  ) async {
+    try {
+      var updatedDeviceInfo = await _apiService.deviceInfoApi.updateDeviceInfo(
+        UpdateDeviceInfoDto(
+          deviceId: deviceId,
+          deviceType: deviceType,
+          isAutoBackup: status,
+        ),
+      );
 
-    return DeviceInfoRemote.fromJson(res.toString());
+      if (updatedDeviceInfo == null) {
+        throw Exception("Error updating device info");
+      }
+
+      return updatedDeviceInfo;
+    } catch (e) {
+      debugPrint("Error setAutoBackup: ${e.toString()}");
+      throw Error();
+    }
   }
 }
 
diff --git a/mobile/lib/modules/backup/ui/album_info_card.dart b/mobile/lib/modules/backup/ui/album_info_card.dart
index 90a1e8976d..370db0c3b2 100644
--- a/mobile/lib/modules/backup/ui/album_info_card.dart
+++ b/mobile/lib/modules/backup/ui/album_info_card.dart
@@ -26,7 +26,9 @@ class AlbumInfoCard extends HookConsumerWidget {
         ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo);
 
     ColorFilter selectedFilter = ColorFilter.mode(
-        Theme.of(context).primaryColor.withAlpha(100), BlendMode.darken);
+      Theme.of(context).primaryColor.withAlpha(100),
+      BlendMode.darken,
+    );
     ColorFilter excludedFilter =
         ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
     ColorFilter unselectedFilter =
@@ -40,7 +42,10 @@ class AlbumInfoCard extends HookConsumerWidget {
           label: const Text(
             "album_info_card_backup_album_included",
             style: TextStyle(
-                fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
+              fontSize: 10,
+              color: Colors.white,
+              fontWeight: FontWeight.bold,
+            ),
           ).tr(),
           backgroundColor: Theme.of(context).primaryColor,
         );
@@ -51,7 +56,10 @@ class AlbumInfoCard extends HookConsumerWidget {
           label: const Text(
             "album_info_card_backup_album_excluded",
             style: TextStyle(
-                fontSize: 10, color: Colors.white, fontWeight: FontWeight.bold),
+              fontSize: 10,
+              color: Colors.white,
+              fontWeight: FontWeight.bold,
+            ),
           ).tr(),
           backgroundColor: Colors.red[300],
         );
@@ -138,15 +146,16 @@ class AlbumInfoCard extends HookConsumerWidget {
                   height: 200,
                   decoration: BoxDecoration(
                     borderRadius: const BorderRadius.only(
-                        topLeft: Radius.circular(12),
-                        topRight: Radius.circular(12)),
+                      topLeft: Radius.circular(12),
+                      topRight: Radius.circular(12),
+                    ),
                     image: DecorationImage(
                       colorFilter: _buildImageFilter(),
                       image: imageData != null
                           ? MemoryImage(imageData!)
                           : const AssetImage(
-                                  'assets/immich-logo-no-outline.png')
-                              as ImageProvider,
+                              'assets/immich-logo-no-outline.png',
+                            ) as ImageProvider,
                       fit: BoxFit.cover,
                     ),
                   ),
@@ -174,9 +183,10 @@ class AlbumInfoCard extends HookConsumerWidget {
                           Text(
                             albumInfo.name,
                             style: TextStyle(
-                                fontSize: 14,
-                                color: Theme.of(context).primaryColor,
-                                fontWeight: FontWeight.bold),
+                              fontSize: 14,
+                              color: Theme.of(context).primaryColor,
+                              fontWeight: FontWeight.bold,
+                            ),
                           ),
                           Padding(
                             padding: const EdgeInsets.only(top: 2.0),
@@ -186,7 +196,9 @@ class AlbumInfoCard extends HookConsumerWidget {
                                       ? " (${'backup_all'.tr()})"
                                       : ""),
                               style: TextStyle(
-                                  fontSize: 12, color: Colors.grey[600]),
+                                fontSize: 12,
+                                color: Colors.grey[600],
+                              ),
                             ),
                           )
                         ],
diff --git a/mobile/lib/modules/backup/ui/backup_info_card.dart b/mobile/lib/modules/backup/ui/backup_info_card.dart
index 300fa1feba..6bc832a5d4 100644
--- a/mobile/lib/modules/backup/ui/backup_info_card.dart
+++ b/mobile/lib/modules/backup/ui/backup_info_card.dart
@@ -5,12 +5,12 @@ class BackupInfoCard extends StatelessWidget {
   final String title;
   final String subtitle;
   final String info;
-  const BackupInfoCard(
-      {Key? key,
-      required this.title,
-      required this.subtitle,
-      required this.info})
-      : super(key: key);
+  const BackupInfoCard({
+    Key? key,
+    required this.title,
+    required this.subtitle,
+    required this.info,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
diff --git a/mobile/lib/modules/backup/views/album_preview_page.dart b/mobile/lib/modules/backup/views/album_preview_page.dart
index 561d177b7b..2266fe8bcc 100644
--- a/mobile/lib/modules/backup/views/album_preview_page.dart
+++ b/mobile/lib/modules/backup/views/album_preview_page.dart
@@ -20,10 +20,13 @@ class AlbumPreviewPage extends HookConsumerWidget {
           await album.getAssetListRange(start: 0, end: album.assetCount);
     }
 
-    useEffect(() {
-      _getAssetsInAlbum();
-      return null;
-    }, []);
+    useEffect(
+      () {
+        _getAssetsInAlbum();
+        return null;
+      },
+      [],
+    );
 
     return Scaffold(
       appBar: AppBar(
@@ -39,9 +42,10 @@ class AlbumPreviewPage extends HookConsumerWidget {
               child: Text(
                 "ID ${album.id}",
                 style: TextStyle(
-                    fontSize: 10,
-                    color: Colors.grey[600],
-                    fontWeight: FontWeight.bold),
+                  fontSize: 10,
+                  color: Colors.grey[600],
+                  fontWeight: FontWeight.bold,
+                ),
               ),
             ),
           ],
@@ -59,9 +63,11 @@ class AlbumPreviewPage extends HookConsumerWidget {
         ),
         itemCount: assets.value.length,
         itemBuilder: (context, index) {
-          Future<Uint8List?> thumbData = assets.value[index]
-              .thumbnailDataWithSize(const ThumbnailSize(200, 200),
-                  quality: 50);
+          Future<Uint8List?> thumbData =
+              assets.value[index].thumbnailDataWithSize(
+            const ThumbnailSize(200, 200),
+            quality: 50,
+          );
 
           return FutureBuilder<Uint8List?>(
             future: thumbData,
diff --git a/mobile/lib/modules/backup/views/backup_album_selection_page.dart b/mobile/lib/modules/backup/views/backup_album_selection_page.dart
index 27cf556842..50a5be8537 100644
--- a/mobile/lib/modules/backup/views/backup_album_selection_page.dart
+++ b/mobile/lib/modules/backup/views/backup_album_selection_page.dart
@@ -17,10 +17,13 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
     final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
     final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
 
-    useEffect(() {
-      ref.read(backupProvider.notifier).getBackupInfo();
-      return null;
-    }, []);
+    useEffect(
+      () {
+        ref.read(backupProvider.notifier).getBackupInfo();
+        return null;
+      },
+      [],
+    );
 
     _buildAlbumSelectionList() {
       if (availableAlbums.isEmpty) {
@@ -42,8 +45,9 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
                   ? const EdgeInsets.only(left: 16.00)
                   : const EdgeInsets.all(0),
               child: AlbumInfoCard(
-                  imageData: thumbnailData,
-                  albumInfo: availableAlbums[index].albumEntity),
+                imageData: thumbnailData,
+                albumInfo: availableAlbums[index].albumEntity,
+              ),
             );
           }),
         ),
@@ -73,13 +77,15 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
             child: Chip(
               visualDensity: VisualDensity.compact,
               shape: RoundedRectangleBorder(
-                  borderRadius: BorderRadius.circular(5)),
+                borderRadius: BorderRadius.circular(5),
+              ),
               label: Text(
                 album.name,
                 style: const TextStyle(
-                    fontSize: 10,
-                    color: Colors.white,
-                    fontWeight: FontWeight.bold),
+                  fontSize: 10,
+                  color: Colors.white,
+                  fontWeight: FontWeight.bold,
+                ),
               ),
               backgroundColor: Theme.of(context).primaryColor,
               deleteIconColor: Colors.white,
@@ -109,13 +115,15 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
             child: Chip(
               visualDensity: VisualDensity.compact,
               shape: RoundedRectangleBorder(
-                  borderRadius: BorderRadius.circular(5)),
+                borderRadius: BorderRadius.circular(5),
+              ),
               label: Text(
                 album.name,
                 style: const TextStyle(
-                    fontSize: 10,
-                    color: Colors.white,
-                    fontWeight: FontWeight.bold),
+                  fontSize: 10,
+                  color: Colors.white,
+                  fontWeight: FontWeight.bold,
+                ),
               ),
               backgroundColor: Colors.red[300],
               deleteIconColor: Colors.white,
@@ -185,9 +193,10 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
                     title: Text(
                       "backup_album_selection_page_total_assets",
                       style: TextStyle(
-                          fontWeight: FontWeight.bold,
-                          fontSize: 14,
-                          color: Colors.grey[700]),
+                        fontWeight: FontWeight.bold,
+                        fontSize: 14,
+                        color: Colors.grey[700],
+                      ),
                     ).tr(),
                     trailing: Text(
                       ref
@@ -234,7 +243,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
                   builder: (BuildContext context) {
                     return AlertDialog(
                       shape: RoundedRectangleBorder(
-                          borderRadius: BorderRadius.circular(12)),
+                        borderRadius: BorderRadius.circular(12),
+                      ),
                       elevation: 5,
                       title: Text(
                         'backup_album_selection_page_selection_info',
@@ -250,7 +260,9 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
                             Text(
                               'backup_album_selection_page_assets_scatter',
                               style: TextStyle(
-                                  fontSize: 14, color: Colors.grey[700]),
+                                fontSize: 14,
+                                color: Colors.grey[700],
+                              ),
                             ).tr(),
                           ],
                         ),
diff --git a/mobile/lib/modules/backup/views/backup_controller_page.dart b/mobile/lib/modules/backup/views/backup_controller_page.dart
index a778e986d4..65377b8538 100644
--- a/mobile/lib/modules/backup/views/backup_controller_page.dart
+++ b/mobile/lib/modules/backup/views/backup_controller_page.dart
@@ -11,7 +11,6 @@ import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/shared/providers/websocket.provider.dart';
 import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
-import 'package:intl/intl.dart';
 import 'package:percent_indicator/linear_percent_indicator.dart';
 
 class BackupControllerPage extends HookConsumerWidget {
@@ -27,16 +26,19 @@ class BackupControllerPage extends HookConsumerWidget {
         ? false
         : true;
 
-    useEffect(() {
-      if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
-        ref.watch(backupProvider.notifier).getBackupInfo();
-      }
+    useEffect(
+      () {
+        if (backupState.backupProgress != BackUpProgressEnum.inProgress) {
+          ref.watch(backupProvider.notifier).getBackupInfo();
+        }
 
-      ref
-          .watch(websocketProvider.notifier)
-          .stopListenToEvent('on_upload_success');
-      return null;
-    }, []);
+        ref
+            .watch(websocketProvider.notifier)
+            .stopListenToEvent('on_upload_success');
+        return null;
+      },
+      [],
+    );
 
     Widget _buildStorageInformation() {
       return ListTile(
@@ -68,10 +70,11 @@ class BackupControllerPage extends HookConsumerWidget {
               Padding(
                 padding: const EdgeInsets.only(top: 12.0),
                 child: const Text('backup_controller_page_storage_format').tr(
-                    args: [
-                      backupState.serverInfo.diskUse,
-                      backupState.serverInfo.diskSize
-                    ]),
+                  args: [
+                    backupState.serverInfo.diskUse,
+                    backupState.serverInfo.diskSize
+                  ],
+                ),
               ),
             ],
           ),
@@ -129,8 +132,10 @@ class BackupControllerPage extends HookConsumerWidget {
                           .setAutoBackup(true);
                     }
                   },
-                  child: Text(backupBtnText,
-                      style: const TextStyle(fontWeight: FontWeight.bold)),
+                  child: Text(
+                    backupBtnText,
+                    style: const TextStyle(fontWeight: FontWeight.bold),
+                  ),
                 ),
               )
             ],
@@ -157,9 +162,10 @@ class BackupControllerPage extends HookConsumerWidget {
           child: Text(
             text.trim().substring(0, text.length - 2),
             style: TextStyle(
-                color: Theme.of(context).primaryColor,
-                fontSize: 12,
-                fontWeight: FontWeight.bold),
+              color: Theme.of(context).primaryColor,
+              fontSize: 12,
+              fontWeight: FontWeight.bold,
+            ),
           ),
         );
       } else {
@@ -168,9 +174,10 @@ class BackupControllerPage extends HookConsumerWidget {
           child: Text(
             "backup_controller_page_none_selected".tr(),
             style: TextStyle(
-                color: Theme.of(context).primaryColor,
-                fontSize: 12,
-                fontWeight: FontWeight.bold),
+              color: Theme.of(context).primaryColor,
+              fontSize: 12,
+              fontWeight: FontWeight.bold,
+            ),
           ),
         );
       }
@@ -190,9 +197,10 @@ class BackupControllerPage extends HookConsumerWidget {
           child: Text(
             text.trim().substring(0, text.length - 2),
             style: TextStyle(
-                color: Colors.red[300],
-                fontSize: 12,
-                fontWeight: FontWeight.bold),
+              color: Colors.red[300],
+              fontSize: 12,
+              fontWeight: FontWeight.bold,
+            ),
           ),
         );
       } else {
@@ -213,9 +221,10 @@ class BackupControllerPage extends HookConsumerWidget {
         borderOnForeground: false,
         child: ListTile(
           minVerticalPadding: 15,
-          title: const Text("backup_controller_page_albums",
-                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20))
-              .tr(),
+          title: const Text(
+            "backup_controller_page_albums",
+            style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
+          ).tr(),
           subtitle: Padding(
             padding: const EdgeInsets.only(top: 8.0),
             child: Column(
@@ -284,9 +293,9 @@ class BackupControllerPage extends HookConsumerWidget {
                     fontWeight: FontWeight.bold,
                     fontSize: 11,
                   ),
-                ).tr(args: [
-                  ref.watch(errorBackupListProvider).length.toString()
-                ]),
+                ).tr(
+                  args: [ref.watch(errorBackupListProvider).length.toString()],
+                ),
                 backgroundColor: Colors.white,
                 onPressed: () {
                   AutoRouter.of(context).push(const FailedBackupStatusRoute());
@@ -331,12 +340,16 @@ class BackupControllerPage extends HookConsumerWidget {
                           child: const Text(
                             'backup_controller_page_filename',
                             style: TextStyle(
-                                fontWeight: FontWeight.bold, fontSize: 10.0),
-                          ).tr(args: [
-                            backupState.currentUploadAsset.fileName,
-                            backupState.currentUploadAsset.fileType
-                                .toLowerCase()
-                          ]),
+                              fontWeight: FontWeight.bold,
+                              fontSize: 10.0,
+                            ),
+                          ).tr(
+                            args: [
+                              backupState.currentUploadAsset.fileName,
+                              backupState.currentUploadAsset.fileType
+                                  .toLowerCase()
+                            ],
+                          ),
                         ),
                       ),
                     ],
@@ -352,16 +365,20 @@ class BackupControllerPage extends HookConsumerWidget {
                           padding: const EdgeInsets.all(6.0),
                           child: const Text(
                             "backup_controller_page_created",
-                            style: const TextStyle(
-                                fontWeight: FontWeight.bold, fontSize: 10.0),
-                          ).tr(args: [
-                            DateFormat.yMMMMd('en_US').format(
-                              DateTime.parse(
-                                backupState.currentUploadAsset.createdAt
-                                    .toString(),
-                              ),
-                            )
-                          ]),
+                            style: TextStyle(
+                              fontWeight: FontWeight.bold,
+                              fontSize: 10.0,
+                            ),
+                          ).tr(
+                            args: [
+                              DateFormat.yMMMMd('en_US').format(
+                                DateTime.parse(
+                                  backupState.currentUploadAsset.createdAt
+                                      .toString(),
+                                ),
+                              )
+                            ],
+                          ),
                         ),
                       ),
                     ],
@@ -406,14 +423,15 @@ class BackupControllerPage extends HookConsumerWidget {
           style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
         ).tr(),
         leading: IconButton(
-            onPressed: () {
-              ref.watch(websocketProvider.notifier).listenUploadEvent();
-              AutoRouter.of(context).pop(true);
-            },
-            splashRadius: 24,
-            icon: const Icon(
-              Icons.arrow_back_ios_rounded,
-            )),
+          onPressed: () {
+            ref.watch(websocketProvider.notifier).listenUploadEvent();
+            AutoRouter.of(context).pop(true);
+          },
+          splashRadius: 24,
+          icon: const Icon(
+            Icons.arrow_back_ios_rounded,
+          ),
+        ),
       ),
       body: Padding(
         padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32),
diff --git a/mobile/lib/modules/backup/views/failed_backup_status_page.dart b/mobile/lib/modules/backup/views/failed_backup_status_page.dart
index 203ea0fad5..c78592fa2c 100644
--- a/mobile/lib/modules/backup/views/failed_backup_status_page.dart
+++ b/mobile/lib/modules/backup/views/failed_backup_status_page.dart
@@ -19,13 +19,14 @@ class FailedBackupStatusPage extends HookConsumerWidget {
           style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
         ),
         leading: IconButton(
-            onPressed: () {
-              AutoRouter.of(context).pop(true);
-            },
-            splashRadius: 24,
-            icon: const Icon(
-              Icons.arrow_back_ios_rounded,
-            )),
+          onPressed: () {
+            AutoRouter.of(context).pop(true);
+          },
+          splashRadius: 24,
+          icon: const Icon(
+            Icons.arrow_back_ios_rounded,
+          ),
+        ),
       ),
       body: ListView.builder(
         shrinkWrap: true,
@@ -92,9 +93,10 @@ class FailedBackupStatusPage extends HookConsumerWidget {
                                   ),
                                 ),
                                 style: TextStyle(
-                                    fontSize: 12,
-                                    fontWeight: FontWeight.w600,
-                                    color: Colors.grey[700]),
+                                  fontSize: 12,
+                                  fontWeight: FontWeight.w600,
+                                  color: Colors.grey[700],
+                                ),
                               ),
                               Icon(
                                 Icons.error,
diff --git a/mobile/lib/modules/home/models/delete_asset_response.model.dart b/mobile/lib/modules/home/models/delete_asset_response.model.dart
deleted file mode 100644
index 6e9105941f..0000000000
--- a/mobile/lib/modules/home/models/delete_asset_response.model.dart
+++ /dev/null
@@ -1,55 +0,0 @@
-import 'dart:convert';
-
-class DeleteAssetResponse {
-  final String id;
-  final String status;
-
-  DeleteAssetResponse({
-    required this.id,
-    required this.status,
-  });
-
-  DeleteAssetResponse copyWith({
-    String? id,
-    String? status,
-  }) {
-    return DeleteAssetResponse(
-      id: id ?? this.id,
-      status: status ?? this.status,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'id': id,
-      'status': status,
-    };
-  }
-
-  factory DeleteAssetResponse.fromMap(Map<String, dynamic> map) {
-    return DeleteAssetResponse(
-      id: map['id'] ?? '',
-      status: map['status'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory DeleteAssetResponse.fromJson(String source) =>
-      DeleteAssetResponse.fromMap(json.decode(source));
-
-  @override
-  String toString() => 'DeleteAssetResponse(id: $id, status: $status)';
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is DeleteAssetResponse &&
-        other.id == id &&
-        other.status == status;
-  }
-
-  @override
-  int get hashCode => id.hashCode ^ status.hashCode;
-}
diff --git a/mobile/lib/modules/home/models/get_all_asset_response.model.dart b/mobile/lib/modules/home/models/get_all_asset_response.model.dart
index 86b340ae36..f02d830b2a 100644
--- a/mobile/lib/modules/home/models/get_all_asset_response.model.dart
+++ b/mobile/lib/modules/home/models/get_all_asset_response.model.dart
@@ -1,11 +1,9 @@
-import 'dart:convert';
-
 import 'package:flutter/foundation.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class ImmichAssetGroupByDate {
   final String date;
-  List<ImmichAsset> assets;
+  List<AssetResponseDto> assets;
   ImmichAssetGroupByDate({
     required this.date,
     required this.assets,
@@ -13,7 +11,7 @@ class ImmichAssetGroupByDate {
 
   ImmichAssetGroupByDate copyWith({
     String? date,
-    List<ImmichAsset>? assets,
+    List<AssetResponseDto>? assets,
   }) {
     return ImmichAssetGroupByDate(
       date: date ?? this.date,
@@ -21,26 +19,6 @@ class ImmichAssetGroupByDate {
     );
   }
 
-  Map<String, dynamic> toMap() {
-    return {
-      'date': date,
-      'assets': assets.map((x) => x.toMap()).toList(),
-    };
-  }
-
-  factory ImmichAssetGroupByDate.fromMap(Map<String, dynamic> map) {
-    return ImmichAssetGroupByDate(
-      date: map['date'] ?? '',
-      assets: List<ImmichAsset>.from(
-          map['assets']?.map((x) => ImmichAsset.fromMap(x))),
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory ImmichAssetGroupByDate.fromJson(String source) =>
-      ImmichAssetGroupByDate.fromMap(json.decode(source));
-
   @override
   String toString() => 'ImmichAssetGroupByDate(date: $date, assets: $assets)';
 
@@ -79,28 +57,6 @@ class GetAllAssetResponse {
     );
   }
 
-  Map<String, dynamic> toMap() {
-    return {
-      'count': count,
-      'data': data.map((x) => x.toMap()).toList(),
-      'nextPageKey': nextPageKey,
-    };
-  }
-
-  factory GetAllAssetResponse.fromMap(Map<String, dynamic> map) {
-    return GetAllAssetResponse(
-      count: map['count']?.toInt() ?? 0,
-      data: List<ImmichAssetGroupByDate>.from(
-          map['data']?.map((x) => ImmichAssetGroupByDate.fromMap(x))),
-      nextPageKey: map['nextPageKey'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory GetAllAssetResponse.fromJson(String source) =>
-      GetAllAssetResponse.fromMap(json.decode(source));
-
   @override
   String toString() =>
       'GetAllAssetResponse(count: $count, data: $data, nextPageKey: $nextPageKey)';
diff --git a/mobile/lib/modules/home/models/home_page_state.model.dart b/mobile/lib/modules/home/models/home_page_state.model.dart
index 95621fc991..1701ac3ffb 100644
--- a/mobile/lib/modules/home/models/home_page_state.model.dart
+++ b/mobile/lib/modules/home/models/home_page_state.model.dart
@@ -1,12 +1,10 @@
-import 'dart:convert';
-
 import 'package:collection/collection.dart';
 
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class HomePageState {
   final bool isMultiSelectEnable;
-  final Set<ImmichAsset> selectedItems;
+  final Set<AssetResponseDto> selectedItems;
   final Set<String> selectedDateGroup;
   HomePageState({
     required this.isMultiSelectEnable,
@@ -16,7 +14,7 @@ class HomePageState {
 
   HomePageState copyWith({
     bool? isMultiSelectEnable,
-    Set<ImmichAsset>? selectedItems,
+    Set<AssetResponseDto>? selectedItems,
     Set<String>? selectedDateGroup,
   }) {
     return HomePageState(
@@ -26,28 +24,6 @@ class HomePageState {
     );
   }
 
-  Map<String, dynamic> toMap() {
-    return {
-      'isMultiSelectEnable': isMultiSelectEnable,
-      'selectedItems': selectedItems.map((x) => x.toMap()).toList(),
-      'selectedDateGroup': selectedDateGroup.toList(),
-    };
-  }
-
-  factory HomePageState.fromMap(Map<String, dynamic> map) {
-    return HomePageState(
-      isMultiSelectEnable: map['isMultiSelectEnable'] ?? false,
-      selectedItems: Set<ImmichAsset>.from(
-          map['selectedItems']?.map((x) => ImmichAsset.fromMap(x))),
-      selectedDateGroup: Set<String>.from(map['selectedDateGroup']),
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory HomePageState.fromJson(String source) =>
-      HomePageState.fromMap(json.decode(source));
-
   @override
   String toString() =>
       'HomePageState(isMultiSelectEnable: $isMultiSelectEnable, selectedItems: $selectedItems, selectedDateGroup: $selectedDateGroup)';
diff --git a/mobile/lib/modules/home/providers/home_page_state.provider.dart b/mobile/lib/modules/home/providers/home_page_state.provider.dart
index 6a9fb71760..f81c7199a0 100644
--- a/mobile/lib/modules/home/providers/home_page_state.provider.dart
+++ b/mobile/lib/modules/home/providers/home_page_state.provider.dart
@@ -1,6 +1,6 @@
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/home/models/home_page_state.model.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class HomePageStateNotifier extends StateNotifier<HomePageState> {
   HomePageStateNotifier()
@@ -14,7 +14,8 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
 
   void addSelectedDateGroup(String dateGroupTitle) {
     state = state.copyWith(
-        selectedDateGroup: {...state.selectedDateGroup, dateGroupTitle});
+      selectedDateGroup: {...state.selectedDateGroup, dateGroupTitle},
+    );
   }
 
   void removeSelectedDateGroup(String dateGroupTitle) {
@@ -25,36 +26,39 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
     state = state.copyWith(selectedDateGroup: currentDateGroup);
   }
 
-  void enableMultiSelect(Set<ImmichAsset> selectedItems) {
+  void enableMultiSelect(Set<AssetResponseDto> selectedItems) {
     state =
         state.copyWith(isMultiSelectEnable: true, selectedItems: selectedItems);
   }
 
   void disableMultiSelect() {
     state = state.copyWith(
-        isMultiSelectEnable: false, selectedItems: {}, selectedDateGroup: {});
+      isMultiSelectEnable: false,
+      selectedItems: {},
+      selectedDateGroup: {},
+    );
   }
 
-  void addSingleSelectedItem(ImmichAsset asset) {
+  void addSingleSelectedItem(AssetResponseDto asset) {
     state = state.copyWith(selectedItems: {...state.selectedItems, asset});
   }
 
-  void addMultipleSelectedItems(List<ImmichAsset> assets) {
+  void addMultipleSelectedItems(List<AssetResponseDto> assets) {
     state = state.copyWith(selectedItems: {...state.selectedItems, ...assets});
   }
 
-  void removeSingleSelectedItem(ImmichAsset asset) {
-    Set<ImmichAsset> currentList = state.selectedItems;
+  void removeSingleSelectedItem(AssetResponseDto asset) {
+    Set<AssetResponseDto> currentList = state.selectedItems;
 
     currentList.removeWhere((e) => e.id == asset.id);
 
     state = state.copyWith(selectedItems: currentList);
   }
 
-  void removeMultipleSelectedItem(List<ImmichAsset> assets) {
-    Set<ImmichAsset> currentList = state.selectedItems;
+  void removeMultipleSelectedItem(List<AssetResponseDto> assets) {
+    Set<AssetResponseDto> currentList = state.selectedItems;
 
-    for (ImmichAsset asset in assets) {
+    for (AssetResponseDto asset in assets) {
       currentList.removeWhere((e) => e.id == asset.id);
     }
 
@@ -64,4 +68,5 @@ class HomePageStateNotifier extends StateNotifier<HomePageState> {
 
 final homePageStateProvider =
     StateNotifierProvider<HomePageStateNotifier, HomePageState>(
-        ((ref) => HomePageStateNotifier()));
+  ((ref) => HomePageStateNotifier()),
+);
diff --git a/mobile/lib/modules/home/providers/upload_profile_image.provider.dart b/mobile/lib/modules/home/providers/upload_profile_image.provider.dart
index f8d21d782c..66060da383 100644
--- a/mobile/lib/modules/home/providers/upload_profile_image.provider.dart
+++ b/mobile/lib/modules/home/providers/upload_profile_image.provider.dart
@@ -73,10 +73,12 @@ class UploadProfileImageState {
 class UploadProfileImageNotifier
     extends StateNotifier<UploadProfileImageState> {
   UploadProfileImageNotifier(this._userSErvice)
-      : super(UploadProfileImageState(
-          profileImagePath: '',
-          status: UploadProfileStatus.idle,
-        ));
+      : super(
+          UploadProfileImageState(
+            profileImagePath: '',
+            status: UploadProfileStatus.idle,
+          ),
+        );
 
   final UserService _userSErvice;
 
@@ -88,8 +90,9 @@ class UploadProfileImageNotifier
     if (res != null) {
       debugPrint("Succesfully upload profile image");
       state = state.copyWith(
-          status: UploadProfileStatus.success,
-          profileImagePath: res.profileImagePath);
+        status: UploadProfileStatus.success,
+        profileImagePath: res.profileImagePath,
+      );
       return true;
     }
 
@@ -100,4 +103,5 @@ class UploadProfileImageNotifier
 
 final uploadProfileImageProvider =
     StateNotifierProvider<UploadProfileImageNotifier, UploadProfileImageState>(
-        ((ref) => UploadProfileImageNotifier(ref.watch(userServiceProvider))));
+  ((ref) => UploadProfileImageNotifier(ref.watch(userServiceProvider))),
+);
diff --git a/mobile/lib/modules/home/services/asset.service.dart b/mobile/lib/modules/home/services/asset.service.dart
index 16546f1dc7..3e44d8b165 100644
--- a/mobile/lib/modules/home/services/asset.service.dart
+++ b/mobile/lib/modules/home/services/asset.service.dart
@@ -1,120 +1,51 @@
-import 'dart:convert';
+import 'dart:async';
 
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/home/models/delete_asset_response.model.dart';
-import 'package:immich_mobile/modules/home/models/get_all_asset_response.model.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:immich_mobile/shared/models/immich_asset_with_exif.model.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
+import 'package:openapi/api.dart';
 
-final assetServiceProvider =
-    Provider((ref) => AssetService(ref.watch(networkServiceProvider)));
+final assetServiceProvider = Provider(
+  (ref) => AssetService(
+    ref.watch(apiServiceProvider),
+  ),
+);
 
 class AssetService {
-  final NetworkService _networkService;
-  AssetService(this._networkService);
+  final ApiService _apiService;
 
-  Future<List<ImmichAsset>?> getAllAsset() async {
-    var res = await _networkService.getRequest(url: "asset/");
+  AssetService(this._apiService);
+
+  Future<List<AssetResponseDto>?> getAllAsset() async {
     try {
-      List<dynamic> decodedData = jsonDecode(res.toString());
-
-      List<ImmichAsset> result =
-          List.from(decodedData.map((a) => ImmichAsset.fromMap(a)));
-      return result;
+      return await _apiService.assetApi.getAllAssets();
     } catch (e) {
-      debugPrint("Error getAllAsset  ${e.toString()}");
-    }
-    return null;
-  }
-
-  Future<GetAllAssetResponse?> getAllAssetWithPagination() async {
-    var res = await _networkService.getRequest(url: "asset/all");
-    try {
-      Map<String, dynamic> decodedData = jsonDecode(res.toString());
-
-      GetAllAssetResponse result = GetAllAssetResponse.fromMap(decodedData);
-      return result;
-    } catch (e) {
-      debugPrint("Error getAllAsset  ${e.toString()}");
-    }
-    return null;
-  }
-
-  Future<GetAllAssetResponse?> getOlderAsset(String? nextPageKey) async {
-    try {
-      var res = await _networkService.getRequest(
-        url: "asset/all?nextPageKey=$nextPageKey",
-      );
-
-      Map<String, dynamic> decodedData = jsonDecode(res.toString());
-
-      GetAllAssetResponse result = GetAllAssetResponse.fromMap(decodedData);
-      if (result.count != 0) {
-        return result;
-      }
-    } catch (e) {
-      debugPrint("Error getAllAsset  ${e.toString()}");
-    }
-    return null;
-  }
-
-  Future<List<ImmichAsset>> getNewAsset(String latestDate) async {
-    try {
-      var res = await _networkService.getRequest(
-        url: "asset/new?latestDate=$latestDate",
-      );
-
-      List<dynamic> decodedData = jsonDecode(res.toString());
-
-      List<ImmichAsset> result =
-          List.from(decodedData.map((a) => ImmichAsset.fromMap(a)));
-      if (result.isNotEmpty) {
-        return result;
-      }
-
-      return [];
-    } catch (e) {
-      debugPrint("Error getAllAsset  ${e.toString()}");
-      return [];
-    }
-  }
-
-  Future<ImmichAssetWithExif?> getAssetById(String assetId) async {
-    try {
-      var res = await _networkService.getRequest(
-        url: "asset/assetById/$assetId",
-      );
-
-      Map<String, dynamic> decodedData = jsonDecode(res.toString());
-
-      ImmichAssetWithExif result = ImmichAssetWithExif.fromMap(decodedData);
-      return result;
-    } catch (e) {
-      debugPrint("Error getAllAsset  ${e.toString()}");
+      debugPrint("Error [getAllAsset]  ${e.toString()}");
       return null;
     }
   }
 
-  Future<List<DeleteAssetResponse>?> deleteAssets(
-      Set<ImmichAsset> deleteAssets) async {
+  Future<AssetResponseDto?> getAssetById(String assetId) async {
     try {
-      var payload = [];
+      return await _apiService.assetApi.getAssetById(assetId);
+    } catch (e) {
+      debugPrint("Error [getAssetById]  ${e.toString()}");
+      return null;
+    }
+  }
+
+  Future<List<DeleteAssetResponseDto>?> deleteAssets(
+    Set<AssetResponseDto> deleteAssets,
+  ) async {
+    try {
+      List<String> payload = [];
 
       for (var asset in deleteAssets) {
         payload.add(asset.id);
       }
 
-      var res = await _networkService
-          .deleteRequest(url: "asset/", data: {"ids": payload});
-
-      List<dynamic> decodedData = jsonDecode(res.toString());
-
-      List<DeleteAssetResponse> result =
-          List.from(decodedData.map((a) => DeleteAssetResponse.fromMap(a)));
-
-      return result;
+      return await _apiService.assetApi
+          .deleteAsset(DeleteAssetDto(ids: payload));
     } catch (e) {
       debugPrint("Error getAllAsset  ${e.toString()}");
       return null;
diff --git a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart
index 2ff56fb997..87b08e2313 100644
--- a/mobile/lib/modules/home/ui/control_bottom_app_bar.dart
+++ b/mobile/lib/modules/home/ui/control_bottom_app_bar.dart
@@ -15,7 +15,9 @@ class ControlBottomAppBar extends StatelessWidget {
         height: MediaQuery.of(context).size.height * 0.15,
         decoration: BoxDecoration(
           borderRadius: const BorderRadius.only(
-              topLeft: Radius.circular(15), topRight: Radius.circular(15)),
+            topLeft: Radius.circular(15),
+            topRight: Radius.circular(15),
+          ),
           color: Colors.grey[300]?.withOpacity(0.98),
         ),
         child: Column(
@@ -48,12 +50,12 @@ class ControlBottomAppBar extends StatelessWidget {
 }
 
 class ControlBoxButton extends StatelessWidget {
-  const ControlBoxButton(
-      {Key? key,
-      required this.label,
-      required this.iconData,
-      required this.onPressed})
-      : super(key: key);
+  const ControlBoxButton({
+    Key? key,
+    required this.label,
+    required this.iconData,
+    required this.onPressed,
+  }) : super(key: key);
 
   final String label;
   final IconData iconData;
diff --git a/mobile/lib/modules/home/ui/daily_title_text.dart b/mobile/lib/modules/home/ui/daily_title_text.dart
index 15e941f9a0..ad2532b416 100644
--- a/mobile/lib/modules/home/ui/daily_title_text.dart
+++ b/mobile/lib/modules/home/ui/daily_title_text.dart
@@ -2,8 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:intl/intl.dart';
+import 'package:openapi/api.dart';
 
 class DailyTitleText extends ConsumerWidget {
   const DailyTitleText({
@@ -13,14 +12,15 @@ class DailyTitleText extends ConsumerWidget {
   }) : super(key: key);
 
   final String isoDate;
-  final List<ImmichAsset> assetGroup;
+  final List<AssetResponseDto> assetGroup;
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     var currentYear = DateTime.now().year;
     var groupYear = DateTime.parse(isoDate).year;
-    var formatDateTemplate =
-        currentYear == groupYear ? "daily_title_text_date".tr() : "daily_title_text_date_year".tr();
+    var formatDateTemplate = currentYear == groupYear
+        ? "daily_title_text_date".tr()
+        : "daily_title_text_date_year".tr();
     var dateText =
         DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
     var isMultiSelectEnable =
@@ -74,7 +74,11 @@ class DailyTitleText extends ConsumerWidget {
     return SliverToBoxAdapter(
       child: Padding(
         padding: const EdgeInsets.only(
-            top: 29.0, bottom: 29.0, left: 12.0, right: 12.0),
+          top: 29.0,
+          bottom: 29.0,
+          left: 12.0,
+          right: 12.0,
+        ),
         child: Row(
           children: [
             Text(
diff --git a/mobile/lib/modules/home/ui/disable_multi_select_button.dart b/mobile/lib/modules/home/ui/disable_multi_select_button.dart
index 3a28d7fe86..9a75bd6376 100644
--- a/mobile/lib/modules/home/ui/disable_multi_select_button.dart
+++ b/mobile/lib/modules/home/ui/disable_multi_select_button.dart
@@ -29,15 +29,18 @@ class DisableMultiSelectButton extends ConsumerWidget {
             child: Padding(
               padding: const EdgeInsets.symmetric(horizontal: 4.0),
               child: TextButton.icon(
-                  onPressed: () {
-                    onPressed();
-                  },
-                  icon: const Icon(Icons.close_rounded),
-                  label: Text(
-                    '$selectedItemCount',
-                    style: const TextStyle(
-                        fontWeight: FontWeight.w600, fontSize: 18),
-                  )),
+                onPressed: () {
+                  onPressed();
+                },
+                icon: const Icon(Icons.close_rounded),
+                label: Text(
+                  '$selectedItemCount',
+                  style: const TextStyle(
+                    fontWeight: FontWeight.w600,
+                    fontSize: 18,
+                  ),
+                ),
+              ),
             ),
           ),
         ),
diff --git a/mobile/lib/modules/home/ui/draggable_scrollbar.dart b/mobile/lib/modules/home/ui/draggable_scrollbar.dart
index b7e075492c..90e7d85e51 100644
--- a/mobile/lib/modules/home/ui/draggable_scrollbar.dart
+++ b/mobile/lib/modules/home/ui/draggable_scrollbar.dart
@@ -118,20 +118,24 @@ class DraggableScrollbar extends StatefulWidget {
     this.labelConstraints,
   })  : assert(child.scrollDirection == Axis.vertical),
         scrollThumbBuilder = _thumbSemicircleBuilder(
-            heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb),
+          heightScrollThumb * 0.6,
+          scrollThumbKey,
+          alwaysVisibleScrollThumb,
+        ),
         super(key: key);
 
   @override
   DraggableScrollbarState createState() => DraggableScrollbarState();
 
-  static buildScrollThumbAndLabel(
-      {required Widget scrollThumb,
-      required Color backgroundColor,
-      required Animation<double>? thumbAnimation,
-      required Animation<double>? labelAnimation,
-      required Text? labelText,
-      required BoxConstraints? labelConstraints,
-      required bool alwaysVisibleScrollThumb}) {
+  static buildScrollThumbAndLabel({
+    required Widget scrollThumb,
+    required Color backgroundColor,
+    required Animation<double>? thumbAnimation,
+    required Animation<double>? labelAnimation,
+    required Text? labelText,
+    required BoxConstraints? labelConstraints,
+    required bool alwaysVisibleScrollThumb,
+  }) {
     var scrollThumbAndLabel = labelText == null
         ? scrollThumb
         : Row(
@@ -158,7 +162,10 @@ class DraggableScrollbar extends StatefulWidget {
   }
 
   static ScrollThumbBuilder _thumbSemicircleBuilder(
-      double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
+    double width,
+    Key? scrollThumbKey,
+    bool alwaysVisibleScrollThumb,
+  ) {
     return (
       Color backgroundColor,
       Animation<double> thumbAnimation,
@@ -198,7 +205,9 @@ class DraggableScrollbar extends StatefulWidget {
   }
 
   static ScrollThumbBuilder _thumbArrowBuilder(
-      Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
+    Key? scrollThumbKey,
+    bool alwaysVisibleScrollThumb,
+  ) {
     return (
       Color backgroundColor,
       Animation<double> thumbAnimation,
@@ -234,7 +243,9 @@ class DraggableScrollbar extends StatefulWidget {
   }
 
   static ScrollThumbBuilder _thumbRRectBuilder(
-      Key? scrollThumbKey, bool alwaysVisibleScrollThumb) {
+    Key? scrollThumbKey,
+    bool alwaysVisibleScrollThumb,
+  ) {
     return (
       Color backgroundColor,
       Animation<double> thumbAnimation,
@@ -372,42 +383,44 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
     }
 
     return LayoutBuilder(
-        builder: (BuildContext context, BoxConstraints constraints) {
-      //print("LayoutBuilder constraints=$constraints");
+      builder: (BuildContext context, BoxConstraints constraints) {
+        //print("LayoutBuilder constraints=$constraints");
 
-      return NotificationListener<ScrollNotification>(
-        onNotification: (ScrollNotification notification) {
-          changePosition(notification);
-          return false;
-        },
-        child: Stack(
-          children: <Widget>[
-            RepaintBoundary(
-              child: widget.child,
-            ),
-            RepaintBoundary(
+        return NotificationListener<ScrollNotification>(
+          onNotification: (ScrollNotification notification) {
+            changePosition(notification);
+            return false;
+          },
+          child: Stack(
+            children: <Widget>[
+              RepaintBoundary(
+                child: widget.child,
+              ),
+              RepaintBoundary(
                 child: GestureDetector(
-              onVerticalDragStart: _onVerticalDragStart,
-              onVerticalDragUpdate: _onVerticalDragUpdate,
-              onVerticalDragEnd: _onVerticalDragEnd,
-              child: Container(
-                alignment: Alignment.topRight,
-                margin: EdgeInsets.only(top: _barOffset),
-                padding: widget.padding,
-                child: widget.scrollThumbBuilder(
-                  widget.backgroundColor,
-                  _thumbAnimation,
-                  _labelAnimation,
-                  widget.heightScrollThumb,
-                  labelText: labelText,
-                  labelConstraints: widget.labelConstraints,
+                  onVerticalDragStart: _onVerticalDragStart,
+                  onVerticalDragUpdate: _onVerticalDragUpdate,
+                  onVerticalDragEnd: _onVerticalDragEnd,
+                  child: Container(
+                    alignment: Alignment.topRight,
+                    margin: EdgeInsets.only(top: _barOffset),
+                    padding: widget.padding,
+                    child: widget.scrollThumbBuilder(
+                      widget.backgroundColor,
+                      _thumbAnimation,
+                      _labelAnimation,
+                      widget.heightScrollThumb,
+                      labelText: labelText,
+                      labelConstraints: widget.labelConstraints,
+                    ),
+                  ),
                 ),
               ),
-            )),
-          ],
-        ),
-      );
-    });
+            ],
+          ),
+        );
+      },
+    );
   }
 
   //scroll bar has received notification that it's view was scrolled
@@ -498,7 +511,10 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
         }
 
         double viewDelta = getScrollViewDelta(
-            details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent);
+          details.delta.dy,
+          barMaxScrollExtent,
+          viewMaxScrollExtent,
+        );
 
         _viewOffset = widget.controller.position.pixels + viewDelta;
         if (_viewOffset < widget.controller.position.minScrollExtent) {
@@ -579,7 +595,9 @@ class ArrowClipper extends CustomClipper<Path> {
     path.lineTo(startPointX + arrowWidth, startPointY);
     path.lineTo(startPointX + arrowWidth, startPointY + 1.0);
     path.lineTo(
-        startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0);
+      startPointX + arrowWidth / 2,
+      startPointY - arrowWidth / 2 + 1.0,
+    );
     path.lineTo(startPointX, startPointY + 1.0);
     path.close();
 
@@ -589,7 +607,9 @@ class ArrowClipper extends CustomClipper<Path> {
     path.lineTo(startPointX, startPointY);
     path.lineTo(startPointX, startPointY - 1.0);
     path.lineTo(
-        startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0);
+      startPointX + arrowWidth / 2,
+      startPointY + arrowWidth / 2 - 1.0,
+    );
     path.lineTo(startPointX + arrowWidth, startPointY - 1.0);
     path.close();
 
diff --git a/mobile/lib/modules/home/ui/image_grid.dart b/mobile/lib/modules/home/ui/image_grid.dart
index 3b8e11adb8..1a7f1b2d71 100644
--- a/mobile/lib/modules/home/ui/image_grid.dart
+++ b/mobile/lib/modules/home/ui/image_grid.dart
@@ -1,10 +1,10 @@
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class ImageGrid extends ConsumerWidget {
-  final List<ImmichAsset> assetGroup;
+  final List<AssetResponseDto> assetGroup;
 
   const ImageGrid({Key? key, required this.assetGroup}) : super(key: key);
 
@@ -25,7 +25,7 @@ class ImageGrid extends ConsumerWidget {
             child: Stack(
               children: [
                 ThumbnailImage(asset: assetGroup[index]),
-                if (assetType != 'IMAGE')
+                if (assetType != AssetTypeEnum.IMAGE)
                   Positioned(
                     top: 5,
                     right: 5,
diff --git a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
index 1f46def56c..7e71cf93f4 100644
--- a/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
+++ b/mobile/lib/modules/home/ui/immich_sliver_appbar.dart
@@ -31,7 +31,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
       pinned: false,
       snap: false,
       shape: const RoundedRectangleBorder(
-          borderRadius: BorderRadius.all(Radius.circular(5))),
+        borderRadius: BorderRadius.all(Radius.circular(5)),
+      ),
       leading: Builder(
         builder: (BuildContext context) {
           return Stack(
@@ -99,7 +100,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
                   child: CircularProgressIndicator(
                     strokeWidth: 1,
                     valueColor: AlwaysStoppedAnimation<Color>(
-                        Theme.of(context).primaryColor),
+                      Theme.of(context).primaryColor,
+                    ),
                   ),
                 ),
               ),
@@ -117,7 +119,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
                         Icons.cloud_off_rounded,
                         size: 8,
                       ),
-                      child: const Icon(Icons.backup_rounded)),
+                      child: const Icon(Icons.backup_rounded),
+                    ),
               onPressed: () async {
                 var onPop = await AutoRouter.of(context)
                     .push(const BackupControllerRoute());
diff --git a/mobile/lib/modules/home/ui/monthly_title_text.dart b/mobile/lib/modules/home/ui/monthly_title_text.dart
index ad9998c8f2..f319624604 100644
--- a/mobile/lib/modules/home/ui/monthly_title_text.dart
+++ b/mobile/lib/modules/home/ui/monthly_title_text.dart
@@ -1,6 +1,5 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
-import 'package:intl/intl.dart';
 
 class MonthlyTitleText extends StatelessWidget {
   const MonthlyTitleText({
@@ -12,7 +11,8 @@ class MonthlyTitleText extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    var monthTitleText = DateFormat("monthly_title_text_date_format".tr()).format(DateTime.parse(isoDate));
+    var monthTitleText = DateFormat("monthly_title_text_date_format".tr())
+        .format(DateTime.parse(isoDate));
 
     return SliverToBoxAdapter(
       child: Padding(
diff --git a/mobile/lib/modules/home/ui/profile_drawer.dart b/mobile/lib/modules/home/ui/profile_drawer.dart
index 20ef4d139a..6154fdc315 100644
--- a/mobile/lib/modules/home/ui/profile_drawer.dart
+++ b/mobile/lib/modules/home/ui/profile_drawer.dart
@@ -55,7 +55,8 @@ class ProfileDrawer extends HookConsumerWidget {
           return CircleAvatar(
             radius: 35,
             backgroundImage: NetworkImage(
-                '$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}'),
+              '$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}',
+            ),
             backgroundColor: Colors.transparent,
           );
         } else {
@@ -71,7 +72,8 @@ class ProfileDrawer extends HookConsumerWidget {
         return CircleAvatar(
           radius: 35,
           backgroundImage: NetworkImage(
-              '$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}'),
+            '$endpoint/user/profile-image/${authState.userId}?d=${dummmy++}',
+          ),
           backgroundColor: Colors.transparent,
         );
       }
@@ -93,7 +95,10 @@ class ProfileDrawer extends HookConsumerWidget {
 
     _pickUserProfileImage() async {
       final XFile? image = await ImagePicker().pickImage(
-          source: ImageSource.gallery, maxHeight: 1024, maxWidth: 1024);
+        source: ImageSource.gallery,
+        maxHeight: 1024,
+        maxWidth: 1024,
+      );
 
       if (image != null) {
         var success =
@@ -101,16 +106,20 @@ class ProfileDrawer extends HookConsumerWidget {
 
         if (success) {
           ref.watch(authenticationProvider.notifier).updateUserProfileImagePath(
-              ref.read(uploadProfileImageProvider).profileImagePath);
+                ref.read(uploadProfileImageProvider).profileImagePath,
+              );
         }
       }
     }
 
-    useEffect(() {
-      _getPackageInfo();
-      _buildUserProfileImage();
-      return null;
-    }, []);
+    useEffect(
+      () {
+        _getPackageInfo();
+        _buildUserProfileImage();
+        return null;
+      },
+      [],
+    );
     return Drawer(
       child: Column(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -186,9 +195,10 @@ class ProfileDrawer extends HookConsumerWidget {
                 title: const Text(
                   "profile_drawer_sign_out",
                   style: TextStyle(
-                      color: Colors.black54,
-                      fontSize: 14,
-                      fontWeight: FontWeight.bold),
+                    color: Colors.black54,
+                    fontSize: 14,
+                    fontWeight: FontWeight.bold,
+                  ),
                 ).tr(),
                 onTap: () async {
                   bool res =
@@ -231,9 +241,10 @@ class ProfileDrawer extends HookConsumerWidget {
                             : "profile_drawer_client_server_up_to_date".tr(),
                         textAlign: TextAlign.center,
                         style: TextStyle(
-                            fontSize: 11,
-                            color: Theme.of(context).primaryColor,
-                            fontWeight: FontWeight.w600),
+                          fontSize: 11,
+                          color: Theme.of(context).primaryColor,
+                          fontWeight: FontWeight.w600,
+                        ),
                       ),
                     ),
                     const Divider(),
@@ -271,7 +282,7 @@ class ProfileDrawer extends HookConsumerWidget {
                           ),
                         ),
                         Text(
-                          "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}",
+                          "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch_}",
                           style: TextStyle(
                             fontSize: 11,
                             color: Colors.grey[500],
diff --git a/mobile/lib/modules/home/ui/thumbnail_image.dart b/mobile/lib/modules/home/ui/thumbnail_image.dart
index ff5af246d6..f86e76505a 100644
--- a/mobile/lib/modules/home/ui/thumbnail_image.dart
+++ b/mobile/lib/modules/home/ui/thumbnail_image.dart
@@ -8,11 +8,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:immich_mobile/routing/router.dart';
+import 'package:openapi/api.dart';
 
 class ThumbnailImage extends HookConsumerWidget {
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
 
   const ThumbnailImage({Key? key, required this.asset}) : super(key: key);
 
@@ -22,14 +22,13 @@ class ThumbnailImage extends HookConsumerWidget {
 
     var box = Hive.box(userInfoBox);
     var thumbnailRequestUrl =
-        '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
-
+        '${box.get(serverEndpointKey)}/asset/thumbnail/${asset.id}';
     var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
     var isMultiSelectEnable =
         ref.watch(homePageStateProvider).isMultiSelectEnable;
     var deviceId = ref.watch(authenticationProvider).deviceId;
 
-    Widget _buildSelectionIcon(ImmichAsset asset) {
+    Widget _buildSelectionIcon(AssetResponseDto asset) {
       if (selectedAsset.contains(asset)) {
         return Icon(
           Icons.check_circle,
@@ -61,7 +60,7 @@ class ThumbnailImage extends HookConsumerWidget {
               .watch(homePageStateProvider.notifier)
               .addSingleSelectedItem(asset);
         } else {
-          if (asset.type == 'IMAGE') {
+          if (asset.type == AssetTypeEnum.IMAGE) {
             AutoRouter.of(context).push(
               ImageViewerRoute(
                 imageUrl:
@@ -74,9 +73,10 @@ class ThumbnailImage extends HookConsumerWidget {
           } else {
             AutoRouter.of(context).push(
               VideoViewerRoute(
-                  videoUrl:
-                      '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
-                  asset: asset),
+                videoUrl:
+                    '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
+                asset: asset,
+              ),
             );
           }
         }
@@ -94,14 +94,16 @@ class ThumbnailImage extends HookConsumerWidget {
               decoration: BoxDecoration(
                 border: isMultiSelectEnable && selectedAsset.contains(asset)
                     ? Border.all(
-                        color: Theme.of(context).primaryColorLight, width: 10)
+                        color: Theme.of(context).primaryColorLight,
+                        width: 10,
+                      )
                     : const Border(),
               ),
               child: CachedNetworkImage(
                 cacheKey: "${asset.id}-${cacheKey.value}",
                 width: 300,
                 height: 300,
-                memCacheHeight: asset.type == 'IMAGE' ? 250 : 400,
+                memCacheHeight: asset.type == AssetTypeEnum.IMAGE ? 250 : 400,
                 fit: BoxFit.cover,
                 imageUrl: thumbnailRequestUrl,
                 httpHeaders: {
@@ -112,9 +114,11 @@ class ThumbnailImage extends HookConsumerWidget {
                     Transform.scale(
                   scale: 0.2,
                   child: CircularProgressIndicator(
-                      value: downloadProgress.progress),
+                    value: downloadProgress.progress,
+                  ),
                 ),
                 errorWidget: (context, url, error) {
+                  debugPrint("Error getting thumbnail $url = $error");
                   return Icon(
                     Icons.image_not_supported_outlined,
                     color: Theme.of(context).primaryColor,
diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart
index 00c1aa8935..66a4834b75 100644
--- a/mobile/lib/modules/home/views/home_page.dart
+++ b/mobile/lib/modules/home/views/home_page.dart
@@ -26,12 +26,15 @@ class HomePage extends HookConsumerWidget {
         ref.watch(homePageStateProvider).isMultiSelectEnable;
     var homePageState = ref.watch(homePageStateProvider);
 
-    useEffect(() {
-      ref.read(websocketProvider.notifier).connect();
-      ref.read(assetProvider.notifier).getAllAsset();
-      ref.watch(serverInfoProvider.notifier).getServerVersion();
-      return null;
-    }, []);
+    useEffect(
+      () {
+        ref.read(websocketProvider.notifier).connect();
+        ref.read(assetProvider.notifier).getAllAsset();
+        ref.watch(serverInfoProvider.notifier).getServerVersion();
+        return null;
+      },
+      [],
+    );
 
     void reloadAllAsset() {
       ref.read(assetProvider.notifier).getAllAsset();
diff --git a/mobile/lib/modules/login/models/authentication_state.model.dart b/mobile/lib/modules/login/models/authentication_state.model.dart
index 6603021bb8..4805d499bd 100644
--- a/mobile/lib/modules/login/models/authentication_state.model.dart
+++ b/mobile/lib/modules/login/models/authentication_state.model.dart
@@ -1,10 +1,8 @@
-import 'dart:convert';
-
-import 'package:immich_mobile/shared/models/device_info.model.dart';
+import 'package:openapi/api.dart';
 
 class AuthenticationState {
   final String deviceId;
-  final String deviceType;
+  final DeviceTypeEnum deviceType;
   final String userId;
   final String userEmail;
   final bool isAuthenticated;
@@ -13,8 +11,7 @@ class AuthenticationState {
   final bool isAdmin;
   final bool shouldChangePassword;
   final String profileImagePath;
-  final DeviceInfoRemote deviceInfo;
-
+  final DeviceInfoResponseDto deviceInfo;
   AuthenticationState({
     required this.deviceId,
     required this.deviceType,
@@ -31,7 +28,7 @@ class AuthenticationState {
 
   AuthenticationState copyWith({
     String? deviceId,
-    String? deviceType,
+    DeviceTypeEnum? deviceType,
     String? userId,
     String? userEmail,
     bool? isAuthenticated,
@@ -40,7 +37,7 @@ class AuthenticationState {
     bool? isAdmin,
     bool? shouldChangePassword,
     String? profileImagePath,
-    DeviceInfoRemote? deviceInfo,
+    DeviceInfoResponseDto? deviceInfo,
   }) {
     return AuthenticationState(
       deviceId: deviceId ?? this.deviceId,
@@ -57,45 +54,6 @@ class AuthenticationState {
     );
   }
 
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll({'deviceId': deviceId});
-    result.addAll({'deviceType': deviceType});
-    result.addAll({'userId': userId});
-    result.addAll({'userEmail': userEmail});
-    result.addAll({'isAuthenticated': isAuthenticated});
-    result.addAll({'firstName': firstName});
-    result.addAll({'lastName': lastName});
-    result.addAll({'isAdmin': isAdmin});
-    result.addAll({'shouldChangePassword': shouldChangePassword});
-    result.addAll({'profileImagePath': profileImagePath});
-    result.addAll({'deviceInfo': deviceInfo.toMap()});
-
-    return result;
-  }
-
-  factory AuthenticationState.fromMap(Map<String, dynamic> map) {
-    return AuthenticationState(
-      deviceId: map['deviceId'] ?? '',
-      deviceType: map['deviceType'] ?? '',
-      userId: map['userId'] ?? '',
-      userEmail: map['userEmail'] ?? '',
-      isAuthenticated: map['isAuthenticated'] ?? false,
-      firstName: map['firstName'] ?? '',
-      lastName: map['lastName'] ?? '',
-      isAdmin: map['isAdmin'] ?? false,
-      shouldChangePassword: map['shouldChangePassword'] ?? false,
-      profileImagePath: map['profileImagePath'] ?? '',
-      deviceInfo: DeviceInfoRemote.fromMap(map['deviceInfo']),
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory AuthenticationState.fromJson(String source) =>
-      AuthenticationState.fromMap(json.decode(source));
-
   @override
   String toString() {
     return 'AuthenticationState(deviceId: $deviceId, deviceType: $deviceType, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, firstName: $firstName, lastName: $lastName, isAdmin: $isAdmin, shouldChangePassword: $shouldChangePassword, profileImagePath: $profileImagePath, deviceInfo: $deviceInfo)';
diff --git a/mobile/lib/modules/login/models/hive_saved_login_info.model.dart b/mobile/lib/modules/login/models/hive_saved_login_info.model.dart
index 471e5df66c..6d367d5978 100644
--- a/mobile/lib/modules/login/models/hive_saved_login_info.model.dart
+++ b/mobile/lib/modules/login/models/hive_saved_login_info.model.dart
@@ -16,9 +16,10 @@ class HiveSavedLoginInfo {
   @HiveField(3)
   bool isSaveLogin;
 
-  HiveSavedLoginInfo(
-      {required this.email,
-      required this.password,
-      required this.serverUrl,
-      required this.isSaveLogin});
+  HiveSavedLoginInfo({
+    required this.email,
+    required this.password,
+    required this.serverUrl,
+    required this.isSaveLogin,
+  });
 }
diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart
index 0634e5731a..885066e220 100644
--- a/mobile/lib/modules/login/providers/authentication.provider.dart
+++ b/mobile/lib/modules/login/providers/authentication.provider.dart
@@ -1,23 +1,23 @@
-import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
 import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
 import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
-import 'package:immich_mobile/modules/login/models/login_response.model.dart';
 import 'package:immich_mobile/modules/backup/services/backup.service.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
 import 'package:immich_mobile/shared/services/device_info.service.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
-import 'package:immich_mobile/shared/models/device_info.model.dart';
+import 'package:openapi/api.dart';
 
 class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
   AuthenticationNotifier(
-      this._deviceInfoService, this._backupService, this._networkService)
-      : super(
+    this._deviceInfoService,
+    this._backupService,
+    this._apiService,
+  ) : super(
           AuthenticationState(
             deviceId: "",
-            deviceType: "",
+            deviceType: DeviceTypeEnum.ANDROID,
             userId: "",
             userEmail: "",
             firstName: '',
@@ -26,12 +26,11 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
             isAdmin: false,
             shouldChangePassword: false,
             isAuthenticated: false,
-            deviceInfo: DeviceInfoRemote(
+            deviceInfo: DeviceInfoResponseDto(
               id: 0,
               userId: "",
               deviceId: "",
-              deviceType: "",
-              notificationToken: "",
+              deviceType: DeviceTypeEnum.ANDROID,
               createdAt: "",
               isAutoBackup: false,
             ),
@@ -40,10 +39,14 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 
   final DeviceInfoService _deviceInfoService;
   final BackupService _backupService;
-  final NetworkService _networkService;
+  final ApiService _apiService;
 
-  Future<bool> login(String email, String password, String serverEndpoint,
-      bool isSavedLoginInfo) async {
+  Future<bool> login(
+    String email,
+    String password,
+    String serverEndpoint,
+    bool isSavedLoginInfo,
+  ) async {
     // Store server endpoint to Hive and test endpoint
     if (serverEndpoint[serverEndpoint.length - 1] == "/") {
       var validUrl = serverEndpoint.substring(0, serverEndpoint.length - 1);
@@ -52,12 +55,12 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
       Hive.box(userInfoBox).put(serverEndpointKey, serverEndpoint);
     }
 
+    // Check Server URL validity
     try {
-      bool isServerEndpointVerified = await _networkService.pingServer();
-      if (!isServerEndpointVerified) {
-        return false;
-      }
+      _apiService.setEndpoint(Hive.box(userInfoBox).get(serverEndpointKey));
+      await _apiService.serverInfoApi.pingServer();
     } catch (e) {
+      debugPrint('Invalid Server Endpoint Url $e');
       return false;
     }
 
@@ -72,56 +75,73 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 
     // Make sign-in request
     try {
-      Response res = await _networkService.postRequest(
-          url: 'auth/login', data: {'email': email, 'password': password});
+      var loginResponse = await _apiService.authenticationApi.login(
+        LoginCredentialDto(
+          email: email,
+          password: password,
+        ),
+      );
 
-      var payload = LogInReponse.fromJson(res.toString());
+      if (loginResponse == null) {
+        debugPrint('Login Response is null');
+        return false;
+      }
 
-      Hive.box(userInfoBox).put(accessTokenKey, payload.accessToken);
+      Hive.box(userInfoBox).put(accessTokenKey, loginResponse.accessToken);
 
       state = state.copyWith(
         isAuthenticated: true,
-        userId: payload.userId,
-        userEmail: payload.userEmail,
-        firstName: payload.firstName,
-        lastName: payload.lastName,
-        profileImagePath: payload.profileImagePath,
-        isAdmin: payload.isAdmin,
-        shouldChangePassword: payload.shouldChangePassword,
+        userId: loginResponse.userId,
+        userEmail: loginResponse.userEmail,
+        firstName: loginResponse.firstName,
+        lastName: loginResponse.lastName,
+        profileImagePath: loginResponse.profileImagePath,
+        isAdmin: loginResponse.isAdmin,
+        shouldChangePassword: loginResponse.shouldChangePassword,
       );
 
+      // Login Success - Set Access Token to API Client
+      _apiService.setAccessToken(loginResponse.accessToken);
+
       if (isSavedLoginInfo) {
         // Save login info to local storage
         Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
           savedLoginInfoKey,
           HiveSavedLoginInfo(
-              email: email,
-              password: password,
-              isSaveLogin: true,
-              serverUrl: Hive.box(userInfoBox).get(serverEndpointKey)),
+            email: email,
+            password: password,
+            isSaveLogin: true,
+            serverUrl: Hive.box(userInfoBox).get(serverEndpointKey),
+          ),
         );
       } else {
         Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox)
             .delete(savedLoginInfoKey);
       }
     } catch (e) {
+      debugPrint("Error logging in $e");
       return false;
     }
 
     // Register device info
     try {
-      Response res = await _networkService.postRequest(
-        url: 'device-info',
-        data: {
-          'deviceId': state.deviceId,
-          'deviceType': state.deviceType,
-        },
+      DeviceInfoResponseDto? deviceInfo =
+          await _apiService.deviceInfoApi.createDeviceInfo(
+        CreateDeviceInfoDto(
+          deviceId: state.deviceId,
+          deviceType: state.deviceType,
+        ),
       );
 
-      DeviceInfoRemote deviceInfo = DeviceInfoRemote.fromJson(res.toString());
+      if (deviceInfo == null) {
+        debugPrint('Device Info Response is null');
+        return false;
+      }
+
       state = state.copyWith(deviceInfo: deviceInfo);
     } catch (e) {
       debugPrint("ERROR Register Device Info: $e");
+      return false;
     }
 
     return true;
@@ -129,27 +149,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
 
   Future<bool> logout() async {
     Hive.box(userInfoBox).delete(accessTokenKey);
-    state = AuthenticationState(
-      deviceId: "",
-      deviceType: "",
-      userId: "",
-      userEmail: "",
-      firstName: '',
-      lastName: '',
-      profileImagePath: '',
-      shouldChangePassword: false,
-      isAuthenticated: false,
-      isAdmin: false,
-      deviceInfo: DeviceInfoRemote(
-        id: 0,
-        userId: "",
-        deviceId: "",
-        deviceType: "",
-        notificationToken: "",
-        createdAt: "",
-        isAutoBackup: false,
-      ),
-    );
+    state = state.copyWith(isAuthenticated: false);
 
     return true;
   }
@@ -157,11 +157,13 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
   setAutoBackup(bool backupState) async {
     var deviceInfo = await _deviceInfoService.getDeviceInfo();
     var deviceId = deviceInfo["deviceId"];
-    var deviceType = deviceInfo["deviceType"];
 
-    DeviceInfoRemote deviceInfoRemote =
+    DeviceTypeEnum deviceType = deviceInfo["deviceType"];
+
+    DeviceInfoResponseDto updatedDeviceInfo =
         await _backupService.setAutoBackup(backupState, deviceId, deviceType);
-    state = state.copyWith(deviceInfo: deviceInfoRemote);
+
+    state = state.copyWith(deviceInfo: updatedDeviceInfo);
   }
 
   updateUserProfileImagePath(String path) {
@@ -169,19 +171,20 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
   }
 
   Future<bool> changePassword(String newPassword) async {
-    Response res = await _networkService.putRequest(
-      url: 'user',
-      data: {
-        'id': state.userId,
-        'password': newPassword,
-        'shouldChangePassword': false,
-      },
-    );
+    try {
+      await _apiService.userApi.updateUser(
+        UpdateUserDto(
+          id: state.userId,
+          password: newPassword,
+          shouldChangePassword: false,
+        ),
+      );
 
-    if (res.statusCode == 200) {
       state = state.copyWith(shouldChangePassword: false);
+
       return true;
-    } else {
+    } catch (e) {
+      debugPrint("Error changing password $e");
       return false;
     }
   }
@@ -192,6 +195,6 @@ final authenticationProvider =
   return AuthenticationNotifier(
     ref.watch(deviceInfoServiceProvider),
     ref.watch(backupServiceProvider),
-    ref.watch(networkServiceProvider),
+    ref.watch(apiServiceProvider),
   );
 });
diff --git a/mobile/lib/modules/login/ui/change_password_form.dart b/mobile/lib/modules/login/ui/change_password_form.dart
index 4e3632e741..0351803d43 100644
--- a/mobile/lib/modules/login/ui/change_password_form.dart
+++ b/mobile/lib/modules/login/ui/change_password_form.dart
@@ -140,35 +140,36 @@ class ChangePasswordButton extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     return ElevatedButton(
-        style: ElevatedButton.styleFrom(
-          visualDensity: VisualDensity.standard,
-          primary: Theme.of(context).primaryColor,
-          onPrimary: Colors.grey[50],
-          elevation: 2,
-          padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
-        ),
-        onPressed: () async {
-          if (formKey.currentState!.validate()) {
-            var isSuccess = await ref
-                .watch(authenticationProvider.notifier)
-                .changePassword(passwordController.value.text);
+      style: ElevatedButton.styleFrom(
+        visualDensity: VisualDensity.standard,
+        primary: Theme.of(context).primaryColor,
+        onPrimary: Colors.grey[50],
+        elevation: 2,
+        padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
+      ),
+      onPressed: () async {
+        if (formKey.currentState!.validate()) {
+          var isSuccess = await ref
+              .watch(authenticationProvider.notifier)
+              .changePassword(passwordController.value.text);
 
-            if (isSuccess) {
-              bool res =
-                  await ref.watch(authenticationProvider.notifier).logout();
+          if (isSuccess) {
+            bool res =
+                await ref.watch(authenticationProvider.notifier).logout();
 
-              if (res) {
-                ref.watch(backupProvider.notifier).cancelBackup();
-                ref.watch(assetProvider.notifier).clearAllAsset();
-                ref.watch(websocketProvider.notifier).disconnect();
-                AutoRouter.of(context).replace(const LoginRoute());
-              }
+            if (res) {
+              ref.watch(backupProvider.notifier).cancelBackup();
+              ref.watch(assetProvider.notifier).clearAllAsset();
+              ref.watch(websocketProvider.notifier).disconnect();
+              AutoRouter.of(context).replace(const LoginRoute());
             }
           }
-        },
-        child: const Text(
-          "Change Password",
-          style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
-        ));
+        }
+      },
+      child: const Text(
+        "Change Password",
+        style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+      ),
+    );
   }
 }
diff --git a/mobile/lib/modules/login/ui/login_form.dart b/mobile/lib/modules/login/ui/login_form.dart
index b109254a77..dad98505df 100644
--- a/mobile/lib/modules/login/ui/login_form.dart
+++ b/mobile/lib/modules/login/ui/login_form.dart
@@ -22,22 +22,25 @@ class LoginForm extends HookConsumerWidget {
     final passwordController =
         useTextEditingController.fromValue(TextEditingValue.empty);
     final serverEndpointController =
-        useTextEditingController(text: 'login_endpoint_hint'.tr());
+        useTextEditingController(text: 'login_form_endpoint_hint'.tr());
     final isSaveLoginInfo = useState<bool>(false);
 
-    useEffect(() {
-      var loginInfo =
-          Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
+    useEffect(
+      () {
+        var loginInfo = Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox)
+            .get(savedLoginInfoKey);
 
-      if (loginInfo != null) {
-        usernameController.text = loginInfo.email;
-        passwordController.text = loginInfo.password;
-        serverEndpointController.text = loginInfo.serverUrl;
-        isSaveLoginInfo.value = loginInfo.isSaveLogin;
-      }
+        if (loginInfo != null) {
+          usernameController.text = loginInfo.email;
+          passwordController.text = loginInfo.password;
+          serverEndpointController.text = loginInfo.serverUrl;
+          isSaveLoginInfo.value = loginInfo.isSaveLogin;
+        }
 
-      return null;
-    }, []);
+        return null;
+      },
+      [],
+    );
 
     return Center(
       child: ConstrainedBox(
@@ -71,14 +74,16 @@ class LoginForm extends HookConsumerWidget {
                 dense: true,
                 side: const BorderSide(color: Colors.grey, width: 1.5),
                 shape: RoundedRectangleBorder(
-                    borderRadius: BorderRadius.circular(5)),
+                  borderRadius: BorderRadius.circular(5),
+                ),
                 enableFeedback: true,
                 title: const Text(
                   "login_form_save_login",
                   style: TextStyle(
-                      fontSize: 16,
-                      fontWeight: FontWeight.bold,
-                      color: Colors.grey),
+                    fontSize: 16,
+                    fontWeight: FontWeight.bold,
+                    color: Colors.grey,
+                  ),
                 ).tr(),
                 value: isSaveLoginInfo.value,
                 onChanged: (switchValue) {
@@ -108,7 +113,6 @@ class ServerEndpointInput extends StatelessWidget {
       : super(key: key);
 
   String? _validateInput(String? url) {
-
     if (url?.startsWith(RegExp(r'https?://')) == true) {
       return null;
     } else {
@@ -122,7 +126,7 @@ class ServerEndpointInput extends StatelessWidget {
       controller: controller,
       decoration: InputDecoration(
         labelText: 'login_form_endpoint_url'.tr(),
-        border: OutlineInputBorder(),
+        border: const OutlineInputBorder(),
         hintText: 'login_form_endpoint_hint'.tr(),
       ),
       validator: _validateInput,
@@ -140,8 +144,9 @@ class EmailInput extends StatelessWidget {
     if (email == null || email == '') return null;
     if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr();
     if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr();
-    if (email.contains(' ') || !email.contains('@'))
+    if (email.contains(' ') || !email.contains('@')) {
       return 'login_form_err_invalid_email'.tr();
+    }
     return null;
   }
 
@@ -151,7 +156,7 @@ class EmailInput extends StatelessWidget {
       controller: controller,
       decoration: InputDecoration(
         labelText: 'login_form_label_email'.tr(),
-        border: OutlineInputBorder(),
+        border: const OutlineInputBorder(),
         hintText: 'login_form_email_hint'.tr(),
       ),
       validator: _validateInput,
@@ -171,9 +176,10 @@ class PasswordInput extends StatelessWidget {
       obscureText: true,
       controller: controller,
       decoration: InputDecoration(
-          labelText: 'login_form_label_password'.tr(),
-          border: OutlineInputBorder(),
-          hintText: 'login_form_password_hint'.tr()),
+        labelText: 'login_form_label_password'.tr(),
+        border: const OutlineInputBorder(),
+        hintText: 'login_form_password_hint'.tr(),
+      ),
     );
   }
 }
@@ -195,43 +201,47 @@ class LoginButton extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     return ElevatedButton(
-        style: ElevatedButton.styleFrom(
-          visualDensity: VisualDensity.standard,
-          primary: Theme.of(context).primaryColor,
-          onPrimary: Colors.grey[50],
-          elevation: 2,
-          padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
-        ),
-        onPressed: () async {
-          // This will remove current cache asset state of previous user login.
-          ref.watch(assetProvider.notifier).clearAllAsset();
+      style: ElevatedButton.styleFrom(
+        visualDensity: VisualDensity.standard,
+        primary: Theme.of(context).primaryColor,
+        onPrimary: Colors.grey[50],
+        elevation: 2,
+        padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25),
+      ),
+      onPressed: () async {
+        // This will remove current cache asset state of previous user login.
+        ref.watch(assetProvider.notifier).clearAllAsset();
 
-          var isAuthenticated = await ref
-              .watch(authenticationProvider.notifier)
-              .login(emailController.text, passwordController.text,
-                  serverEndpointController.text, isSavedLoginInfo);
+        var isAuthenticated =
+            await ref.watch(authenticationProvider.notifier).login(
+                  emailController.text,
+                  passwordController.text,
+                  serverEndpointController.text,
+                  isSavedLoginInfo,
+                );
 
-          if (isAuthenticated) {
-            // Resume backup (if enable) then navigate
+        if (isAuthenticated) {
+          // Resume backup (if enable) then navigate
 
-            if (ref.watch(authenticationProvider).shouldChangePassword &&
-                !ref.watch(authenticationProvider).isAdmin) {
-              AutoRouter.of(context).push(const ChangePasswordRoute());
-            } else {
-              ref.watch(backupProvider.notifier).resumeBackup();
-              AutoRouter.of(context).pushNamed("/tab-controller-page");
-            }
+          if (ref.watch(authenticationProvider).shouldChangePassword &&
+              !ref.watch(authenticationProvider).isAdmin) {
+            AutoRouter.of(context).push(const ChangePasswordRoute());
           } else {
-            ImmichToast.show(
-              context: context,
-              msg: "login_failed".tr(),
-              toastType: ToastType.error,
-            );
+            ref.watch(backupProvider.notifier).resumeBackup();
+            AutoRouter.of(context).pushNamed("/tab-controller-page");
           }
-        },
-        child: const Text(
-          "login_form_button_text",
-          style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
-        ).tr());
+        } else {
+          ImmichToast.show(
+            context: context,
+            msg: "login_form_failed_login".tr(),
+            toastType: ToastType.error,
+          );
+        }
+      },
+      child: const Text(
+        "login_form_button_text",
+        style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+      ).tr(),
+    );
   }
 }
diff --git a/mobile/lib/modules/search/models/curated_location.model.dart b/mobile/lib/modules/search/models/curated_location.model.dart
deleted file mode 100644
index a3baebbdf4..0000000000
--- a/mobile/lib/modules/search/models/curated_location.model.dart
+++ /dev/null
@@ -1,84 +0,0 @@
-import 'dart:convert';
-
-class CuratedLocation {
-  final String id;
-  final String city;
-  final String resizePath;
-  final String deviceAssetId;
-  final String deviceId;
-
-  CuratedLocation({
-    required this.id,
-    required this.city,
-    required this.resizePath,
-    required this.deviceAssetId,
-    required this.deviceId,
-  });
-
-  CuratedLocation copyWith({
-    String? id,
-    String? city,
-    String? resizePath,
-    String? deviceAssetId,
-    String? deviceId,
-  }) {
-    return CuratedLocation(
-      id: id ?? this.id,
-      city: city ?? this.city,
-      resizePath: resizePath ?? this.resizePath,
-      deviceAssetId: deviceAssetId ?? this.deviceAssetId,
-      deviceId: deviceId ?? this.deviceId,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'id': id,
-      'city': city,
-      'resizePath': resizePath,
-      'deviceAssetId': deviceAssetId,
-      'deviceId': deviceId,
-    };
-  }
-
-  factory CuratedLocation.fromMap(Map<String, dynamic> map) {
-    return CuratedLocation(
-      id: map['id'] ?? '',
-      city: map['city'] ?? '',
-      resizePath: map['resizePath'] ?? '',
-      deviceAssetId: map['deviceAssetId'] ?? '',
-      deviceId: map['deviceId'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory CuratedLocation.fromJson(String source) =>
-      CuratedLocation.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'CuratedLocation(id: $id, city: $city, resizePath: $resizePath, deviceAssetId: $deviceAssetId, deviceId: $deviceId)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is CuratedLocation &&
-        other.id == id &&
-        other.city == city &&
-        other.resizePath == resizePath &&
-        other.deviceAssetId == deviceAssetId &&
-        other.deviceId == deviceId;
-  }
-
-  @override
-  int get hashCode {
-    return id.hashCode ^
-        city.hashCode ^
-        resizePath.hashCode ^
-        deviceAssetId.hashCode ^
-        deviceId.hashCode;
-  }
-}
diff --git a/mobile/lib/modules/search/models/curated_object.model.dart b/mobile/lib/modules/search/models/curated_object.model.dart
deleted file mode 100644
index c389aaf92e..0000000000
--- a/mobile/lib/modules/search/models/curated_object.model.dart
+++ /dev/null
@@ -1,85 +0,0 @@
-import 'dart:convert';
-
-class CuratedObject {
-  final String id;
-  final String object;
-  final String resizePath;
-  final String deviceAssetId;
-  final String deviceId;
-  CuratedObject({
-    required this.id,
-    required this.object,
-    required this.resizePath,
-    required this.deviceAssetId,
-    required this.deviceId,
-  });
-
-  CuratedObject copyWith({
-    String? id,
-    String? object,
-    String? resizePath,
-    String? deviceAssetId,
-    String? deviceId,
-  }) {
-    return CuratedObject(
-      id: id ?? this.id,
-      object: object ?? this.object,
-      resizePath: resizePath ?? this.resizePath,
-      deviceAssetId: deviceAssetId ?? this.deviceAssetId,
-      deviceId: deviceId ?? this.deviceId,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll({'id': id});
-    result.addAll({'object': object});
-    result.addAll({'resizePath': resizePath});
-    result.addAll({'deviceAssetId': deviceAssetId});
-    result.addAll({'deviceId': deviceId});
-
-    return result;
-  }
-
-  factory CuratedObject.fromMap(Map<String, dynamic> map) {
-    return CuratedObject(
-      id: map['id'] ?? '',
-      object: map['object'] ?? '',
-      resizePath: map['resizePath'] ?? '',
-      deviceAssetId: map['deviceAssetId'] ?? '',
-      deviceId: map['deviceId'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory CuratedObject.fromJson(String source) =>
-      CuratedObject.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'CuratedObject(id: $id, object: $object, resizePath: $resizePath, deviceAssetId: $deviceAssetId, deviceId: $deviceId)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is CuratedObject &&
-        other.id == id &&
-        other.object == object &&
-        other.resizePath == resizePath &&
-        other.deviceAssetId == deviceAssetId &&
-        other.deviceId == deviceId;
-  }
-
-  @override
-  int get hashCode {
-    return id.hashCode ^
-        object.hashCode ^
-        resizePath.hashCode ^
-        deviceAssetId.hashCode ^
-        deviceId.hashCode;
-  }
-}
diff --git a/mobile/lib/modules/search/models/search_result_page_state.model.dart b/mobile/lib/modules/search/models/search_result_page_state.model.dart
index 4911bb0e6f..1d79dff4b5 100644
--- a/mobile/lib/modules/search/models/search_result_page_state.model.dart
+++ b/mobile/lib/modules/search/models/search_result_page_state.model.dart
@@ -1,13 +1,13 @@
 import 'dart:convert';
 
 import 'package:collection/collection.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class SearchResultPageState {
   final bool isLoading;
   final bool isSuccess;
   final bool isError;
-  final List<ImmichAsset> searchResult;
+  final List<AssetResponseDto> searchResult;
 
   SearchResultPageState({
     required this.isLoading,
@@ -20,7 +20,7 @@ class SearchResultPageState {
     bool? isLoading,
     bool? isSuccess,
     bool? isError,
-    List<ImmichAsset>? searchResult,
+    List<AssetResponseDto>? searchResult,
   }) {
     return SearchResultPageState(
       isLoading: isLoading ?? this.isLoading,
@@ -35,7 +35,7 @@ class SearchResultPageState {
       'isLoading': isLoading,
       'isSuccess': isSuccess,
       'isError': isError,
-      'searchResult': searchResult.map((x) => x.toMap()).toList(),
+      'searchResult': searchResult.map((x) => x.toJson()).toList(),
     };
   }
 
@@ -44,8 +44,9 @@ class SearchResultPageState {
       isLoading: map['isLoading'] ?? false,
       isSuccess: map['isSuccess'] ?? false,
       isError: map['isError'] ?? false,
-      searchResult: List<ImmichAsset>.from(
-          map['searchResult']?.map((x) => ImmichAsset.fromMap(x))),
+      searchResult: List<AssetResponseDto>.from(
+        map['searchResult']?.map((x) => AssetResponseDto.mapFromJson(x)),
+      ),
     );
   }
 
diff --git a/mobile/lib/modules/search/providers/search_page_state.provider.dart b/mobile/lib/modules/search/providers/search_page_state.provider.dart
index dc61aad1a8..d66b6b280b 100644
--- a/mobile/lib/modules/search/providers/search_page_state.provider.dart
+++ b/mobile/lib/modules/search/providers/search_page_state.provider.dart
@@ -1,9 +1,8 @@
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
-import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
 import 'package:immich_mobile/modules/search/models/search_page_state.model.dart';
 
 import 'package:immich_mobile/modules/search/services/search.service.dart';
+import 'package:openapi/api.dart';
 
 class SearchPageStateNotifier extends StateNotifier<SearchPageState> {
   SearchPageStateNotifier(this._searchService)
@@ -58,7 +57,7 @@ final searchPageStateProvider =
 });
 
 final getCuratedLocationProvider =
-    FutureProvider.autoDispose<List<CuratedLocation>>((ref) async {
+    FutureProvider.autoDispose<List<CuratedLocationsResponseDto>>((ref) async {
   final SearchService searchService = ref.watch(searchServiceProvider);
 
   var curatedLocation = await searchService.getCuratedLocation();
@@ -66,7 +65,7 @@ final getCuratedLocationProvider =
 });
 
 final getCuratedObjectProvider =
-    FutureProvider.autoDispose<List<CuratedObject>>((ref) async {
+    FutureProvider.autoDispose<List<CuratedObjectsResponseDto>>((ref) async {
   final SearchService searchService = ref.watch(searchServiceProvider);
 
   var curatedObject = await searchService.getCuratedObjects();
diff --git a/mobile/lib/modules/search/providers/search_result_page.provider.dart b/mobile/lib/modules/search/providers/search_result_page.provider.dart
index eb9f9c3daf..d6984a4b89 100644
--- a/mobile/lib/modules/search/providers/search_result_page.provider.dart
+++ b/mobile/lib/modules/search/providers/search_result_page.provider.dart
@@ -3,8 +3,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/search/models/search_result_page_state.model.dart';
 
 import 'package:immich_mobile/modules/search/services/search.service.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:intl/intl.dart';
+import 'package:openapi/api.dart';
 
 class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
   SearchResultPageNotifier(this._searchService)
@@ -21,19 +21,29 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
 
   void search(String searchTerm) async {
     state = state.copyWith(
-        searchResult: [], isError: false, isLoading: true, isSuccess: false);
+      searchResult: [],
+      isError: false,
+      isLoading: true,
+      isSuccess: false,
+    );
 
-    List<ImmichAsset>? assets = await _searchService.searchAsset(searchTerm);
+    List<AssetResponseDto>? assets =
+        await _searchService.searchAsset(searchTerm);
 
     if (assets != null) {
       state = state.copyWith(
-          searchResult: assets,
-          isError: false,
-          isLoading: false,
-          isSuccess: true);
+        searchResult: assets,
+        isError: false,
+        isLoading: false,
+        isSuccess: true,
+      );
     } else {
       state = state.copyWith(
-          searchResult: [], isError: true, isLoading: false, isSuccess: false);
+        searchResult: [],
+        isError: true,
+        isLoading: false,
+        isSuccess: false,
+      );
     }
   }
 }
@@ -48,7 +58,11 @@ final searchResultGroupByDateTimeProvider = StateProvider((ref) {
   var assets = ref.watch(searchResultPageProvider).searchResult;
 
   assets.sortByCompare<DateTime>(
-      (e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
-  return assets.groupListsBy((element) =>
-      DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
+    (e) => DateTime.parse(e.createdAt),
+    (a, b) => b.compareTo(a),
+  );
+  return assets.groupListsBy(
+    (element) =>
+        DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)),
+  );
 });
diff --git a/mobile/lib/modules/search/services/search.service.dart b/mobile/lib/modules/search/services/search.service.dart
index 06e6bab6f1..83e6122a80 100644
--- a/mobile/lib/modules/search/services/search.service.dart
+++ b/mobile/lib/modules/search/services/search.service.dart
@@ -1,79 +1,54 @@
-import 'dart:convert';
-
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
-import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
+import 'package:openapi/api.dart';
 
-final searchServiceProvider =
-    Provider((ref) => SearchService(ref.watch(networkServiceProvider)));
+final searchServiceProvider = Provider(
+  (ref) => SearchService(
+    ref.watch(apiServiceProvider),
+  ),
+);
 
 class SearchService {
-  final NetworkService _networkService;
-  SearchService(this._networkService);
+  final ApiService _apiService;
+  SearchService(this._apiService);
 
   Future<List<String>?> getUserSuggestedSearchTerms() async {
     try {
-      var res = await _networkService.getRequest(url: "asset/searchTerm");
-      List<dynamic> decodedData = jsonDecode(res.toString());
-
-      return List.from(decodedData);
+      return await _apiService.assetApi.getAssetSearchTerms();
     } catch (e) {
       debugPrint("[ERROR] [getUserSuggestedSearchTerms] ${e.toString()}");
       return [];
     }
   }
 
-  Future<List<ImmichAsset>?> searchAsset(String searchTerm) async {
+  Future<List<AssetResponseDto>?> searchAsset(String searchTerm) async {
     try {
-      var res = await _networkService.postRequest(
-        url: "asset/search",
-        data: {"searchTerm": searchTerm},
-      );
-
-      List<dynamic> decodedData = jsonDecode(res.toString());
-
-      List<ImmichAsset> result =
-          List.from(decodedData.map((a) => ImmichAsset.fromMap(a)));
-
-      return result;
+      return await _apiService.assetApi
+          .searchAsset(SearchAssetDto(searchTerm: searchTerm));
     } catch (e) {
       debugPrint("[ERROR] [searchAsset] ${e.toString()}");
       return null;
     }
   }
 
-  Future<List<CuratedLocation>?> getCuratedLocation() async {
+  Future<List<CuratedLocationsResponseDto>?> getCuratedLocation() async {
     try {
-      var res = await _networkService.getRequest(url: "asset/allLocation");
+      var locations = await _apiService.assetApi.getCuratedLocations();
 
-      List<dynamic> decodedData = jsonDecode(res.toString());
-
-      List<CuratedLocation> result =
-          List.from(decodedData.map((a) => CuratedLocation.fromMap(a)));
-
-      return result;
+      return locations;
     } catch (e) {
-      debugPrint("[ERROR] [getCuratedLocation] ${e.toString()}");
-      throw Error();
+      debugPrint("Error [getCuratedLocation] ${e.toString()}");
+      return [];
     }
   }
 
-  Future<List<CuratedObject>?> getCuratedObjects() async {
+  Future<List<CuratedObjectsResponseDto>?> getCuratedObjects() async {
     try {
-      var res = await _networkService.getRequest(url: "asset/allObjects");
-
-      List<dynamic> decodedData = jsonDecode(res.toString());
-
-      List<CuratedObject> result =
-          List.from(decodedData.map((a) => CuratedObject.fromMap(a)));
-
-      return result;
+      return await _apiService.assetApi.getCuratedObjects();
     } catch (e) {
-      debugPrint("[ERROR] [CuratedObject] ${e.toString()}");
-      throw Error();
+      debugPrint("Error [getCuratedObjects] ${e.toString()}");
+      throw [];
     }
   }
 }
diff --git a/mobile/lib/modules/search/ui/search_bar.dart b/mobile/lib/modules/search/ui/search_bar.dart
index f1b67749af..f9883f65e0 100644
--- a/mobile/lib/modules/search/ui/search_bar.dart
+++ b/mobile/lib/modules/search/ui/search_bar.dart
@@ -5,9 +5,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
 
 class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
-  SearchBar(
-      {Key? key, required this.searchFocusNode, required this.onSubmitted})
-      : super(key: key);
+  SearchBar({
+    Key? key,
+    required this.searchFocusNode,
+    required this.onSubmitted,
+  }) : super(key: key);
 
   final FocusNode searchFocusNode;
   final Function(String) onSubmitted;
@@ -26,7 +28,8 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
                 ref.watch(searchPageStateProvider.notifier).disableSearch();
                 searchTermController.clear();
               },
-              icon: const Icon(Icons.arrow_back_ios_rounded))
+              icon: const Icon(Icons.arrow_back_ios_rounded),
+            )
           : const Icon(Icons.search_rounded),
       title: TextField(
         controller: searchTermController,
@@ -50,10 +53,10 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
         },
         decoration: InputDecoration(
           hintText: 'search_bar_hint'.tr(),
-          enabledBorder: UnderlineInputBorder(
+          enabledBorder: const UnderlineInputBorder(
             borderSide: BorderSide(color: Colors.transparent),
           ),
-          focusedBorder: UnderlineInputBorder(
+          focusedBorder: const UnderlineInputBorder(
             borderSide: BorderSide(color: Colors.transparent),
           ),
         ),
diff --git a/mobile/lib/modules/search/ui/thumbnail_with_info.dart b/mobile/lib/modules/search/ui/thumbnail_with_info.dart
index 36956b9a74..d65db0224c 100644
--- a/mobile/lib/modules/search/ui/thumbnail_with_info.dart
+++ b/mobile/lib/modules/search/ui/thumbnail_with_info.dart
@@ -2,15 +2,14 @@ import 'package:cached_network_image/cached_network_image.dart';
 import 'package:flutter/material.dart';
 import 'package:hive_flutter/hive_flutter.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/utils/capitalize_first_letter.dart';
 
 class ThumbnailWithInfo extends StatelessWidget {
-  const ThumbnailWithInfo(
-      {Key? key,
-      required this.textInfo,
-      required this.imageUrl,
-      required this.onTap})
-      : super(key: key);
+  const ThumbnailWithInfo({
+    Key? key,
+    required this.textInfo,
+    required this.imageUrl,
+    required this.onTap,
+  }) : super(key: key);
 
   final String textInfo;
   final String imageUrl;
diff --git a/mobile/lib/modules/search/views/search_page.dart b/mobile/lib/modules/search/views/search_page.dart
index 9a2a5fb54d..832c641781 100644
--- a/mobile/lib/modules/search/views/search_page.dart
+++ b/mobile/lib/modules/search/views/search_page.dart
@@ -5,8 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hive_flutter/hive_flutter.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
-import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
 import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
 import 'package:immich_mobile/modules/search/ui/search_bar.dart';
 import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
@@ -14,6 +12,7 @@ import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
 import 'package:immich_mobile/utils/capitalize_first_letter.dart';
+import 'package:openapi/api.dart';
 
 // ignore: must_be_immutable
 class SearchPage extends HookConsumerWidget {
@@ -25,15 +24,18 @@ class SearchPage extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     var box = Hive.box(userInfoBox);
     final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
-    AsyncValue<List<CuratedLocation>> curatedLocation =
+    AsyncValue<List<CuratedLocationsResponseDto>> curatedLocation =
         ref.watch(getCuratedLocationProvider);
-    AsyncValue<List<CuratedObject>> curatedObjects =
+    AsyncValue<List<CuratedObjectsResponseDto>> curatedObjects =
         ref.watch(getCuratedObjectProvider);
 
-    useEffect(() {
-      searchFocusNode = FocusNode();
-      return () => searchFocusNode.dispose();
-    }, []);
+    useEffect(
+      () {
+        searchFocusNode = FocusNode();
+        return () => searchFocusNode.dispose();
+      },
+      [],
+    );
 
     _onSearchSubmitted(String searchTerm) async {
       searchFocusNode.unfocus();
@@ -58,16 +60,16 @@ class SearchPage extends HookConsumerWidget {
                     scrollDirection: Axis.horizontal,
                     itemCount: curatedLocation.value?.length,
                     itemBuilder: ((context, index) {
-                      CuratedLocation locationInfo = curatedLocations[index];
+                      var locationInfo = curatedLocations[index];
                       var thumbnailRequestUrl =
-                          '${box.get(serverEndpointKey)}/asset/file?aid=${locationInfo.deviceAssetId}&did=${locationInfo.deviceId}&isThumb=true';
-
+                          '${box.get(serverEndpointKey)}/asset/thumbnail/${locationInfo.id}';
                       return ThumbnailWithInfo(
                         imageUrl: thumbnailRequestUrl,
                         textInfo: locationInfo.city,
                         onTap: () {
                           AutoRouter.of(context).push(
-                              SearchResultRoute(searchTerm: locationInfo.city));
+                            SearchResultRoute(searchTerm: locationInfo.city),
+                          );
                         },
                       );
                     }),
@@ -109,7 +111,7 @@ class SearchPage extends HookConsumerWidget {
                     scrollDirection: Axis.horizontal,
                     itemCount: curatedObjects.value?.length,
                     itemBuilder: ((context, index) {
-                      CuratedObject curatedObjectInfo = objects[index];
+                      var curatedObjectInfo = objects[index];
                       var thumbnailRequestUrl =
                           '${box.get(serverEndpointKey)}/asset/file?aid=${curatedObjectInfo.deviceAssetId}&did=${curatedObjectInfo.deviceId}&isThumb=true';
 
@@ -117,9 +119,12 @@ class SearchPage extends HookConsumerWidget {
                         imageUrl: thumbnailRequestUrl,
                         textInfo: curatedObjectInfo.object,
                         onTap: () {
-                          AutoRouter.of(context).push(SearchResultRoute(
+                          AutoRouter.of(context).push(
+                            SearchResultRoute(
                               searchTerm: curatedObjectInfo.object
-                                  .capitalizeFirstLetter()));
+                                  .capitalizeFirstLetter(),
+                            ),
+                          );
                         },
                       );
                     }),
@@ -160,7 +165,7 @@ class SearchPage extends HookConsumerWidget {
             ListView(
               children: [
                 Padding(
-                  padding: EdgeInsets.all(16.0),
+                  padding: const EdgeInsets.all(16.0),
                   child: const Text(
                     "search_page_places",
                     style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
@@ -168,8 +173,8 @@ class SearchPage extends HookConsumerWidget {
                 ),
                 _buildPlaces(),
                 Padding(
-                  padding: EdgeInsets.all(16.0),
-                  child: const  Text(
+                  padding: const EdgeInsets.all(16.0),
+                  child: const Text(
                     "search_page_things",
                     style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                   ).tr(),
diff --git a/mobile/lib/modules/search/views/search_result_page.dart b/mobile/lib/modules/search/views/search_result_page.dart
index 7fd14abfa9..ffcd7c80b5 100644
--- a/mobile/lib/modules/search/views/search_result_page.dart
+++ b/mobile/lib/modules/search/views/search_result_page.dart
@@ -29,13 +29,18 @@ class SearchResultPage extends HookConsumerWidget {
 
     late FocusNode searchFocusNode;
 
-    useEffect(() {
-      searchFocusNode = FocusNode();
+    useEffect(
+      () {
+        searchFocusNode = FocusNode();
 
-      Future.delayed(Duration.zero,
-          () => ref.read(searchResultPageProvider.notifier).search(searchTerm));
-      return () => searchFocusNode.dispose();
-    }, []);
+        Future.delayed(
+          Duration.zero,
+          () => ref.read(searchResultPageProvider.notifier).search(searchTerm),
+        );
+        return () => searchFocusNode.dispose();
+      },
+      [],
+    );
 
     _onSearchSubmitted(String newSearchTerm) {
       debugPrint("Re-Search with $newSearchTerm");
@@ -69,10 +74,10 @@ class SearchResultPage extends HookConsumerWidget {
         },
         decoration: InputDecoration(
           hintText: 'search_result_page_new_search_hint'.tr(),
-          enabledBorder: UnderlineInputBorder(
+          enabledBorder: const UnderlineInputBorder(
             borderSide: BorderSide(color: Colors.transparent),
           ),
-          focusedBorder: UnderlineInputBorder(
+          focusedBorder: const UnderlineInputBorder(
             borderSide: BorderSide(color: Colors.transparent),
           ),
         ),
@@ -90,9 +95,10 @@ class SearchResultPage extends HookConsumerWidget {
             Text(
               currentSearchTerm.value,
               style: TextStyle(
-                  color: Theme.of(context).primaryColor,
-                  fontSize: 13,
-                  fontWeight: FontWeight.bold),
+                color: Theme.of(context).primaryColor,
+                fontSize: 13,
+                fontWeight: FontWeight.bold,
+              ),
               maxLines: 1,
             ),
             Icon(
@@ -116,9 +122,10 @@ class SearchResultPage extends HookConsumerWidget {
 
       if (searchResultPageState.isLoading) {
         return Center(
-            child: SpinKitDancingSquare(
-          color: Theme.of(context).primaryColor,
-        ));
+          child: SpinKitDancingSquare(
+            color: Theme.of(context).primaryColor,
+          ),
+        );
       }
 
       if (searchResultPageState.isSuccess) {
@@ -184,11 +191,12 @@ class SearchResultPage extends HookConsumerWidget {
           icon: const Icon(Icons.arrow_back_ios_rounded),
         ),
         title: GestureDetector(
-            onTap: () {
-              isNewSearch.value = true;
-              searchFocusNode.requestFocus();
-            },
-            child: isNewSearch.value ? _buildTextField() : _buildChip()),
+          onTap: () {
+            isNewSearch.value = true;
+            searchFocusNode.requestFocus();
+          },
+          child: isNewSearch.value ? _buildTextField() : _buildChip(),
+        ),
         centerTitle: false,
       ),
       body: GestureDetector(
diff --git a/mobile/lib/modules/sharing/models/asset_selection_page_result.model.dart b/mobile/lib/modules/sharing/models/asset_selection_page_result.model.dart
index faea8d0eca..e2fd8e3d12 100644
--- a/mobile/lib/modules/sharing/models/asset_selection_page_result.model.dart
+++ b/mobile/lib/modules/sharing/models/asset_selection_page_result.model.dart
@@ -1,12 +1,10 @@
-import 'dart:convert';
-
 import 'package:collection/collection.dart';
 
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class AssetSelectionPageResult {
-  final Set<ImmichAsset> selectedNewAsset;
-  final Set<ImmichAsset> selectedAdditionalAsset;
+  final Set<AssetResponseDto> selectedNewAsset;
+  final Set<AssetResponseDto> selectedAdditionalAsset;
   final bool isAlbumExist;
 
   AssetSelectionPageResult({
@@ -16,8 +14,8 @@ class AssetSelectionPageResult {
   });
 
   AssetSelectionPageResult copyWith({
-    Set<ImmichAsset>? selectedNewAsset,
-    Set<ImmichAsset>? selectedAdditionalAsset,
+    Set<AssetResponseDto>? selectedNewAsset,
+    Set<AssetResponseDto>? selectedAdditionalAsset,
     bool? isAlbumExist,
   }) {
     return AssetSelectionPageResult(
@@ -28,35 +26,6 @@ class AssetSelectionPageResult {
     );
   }
 
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll(
-        {'selectedNewAsset': selectedNewAsset.map((x) => x.toMap()).toList()});
-    result.addAll({
-      'selectedAdditionalAsset':
-          selectedAdditionalAsset.map((x) => x.toMap()).toList()
-    });
-    result.addAll({'isAlbumExist': isAlbumExist});
-
-    return result;
-  }
-
-  factory AssetSelectionPageResult.fromMap(Map<String, dynamic> map) {
-    return AssetSelectionPageResult(
-      selectedNewAsset: Set<ImmichAsset>.from(
-          map['selectedNewAsset']?.map((x) => ImmichAsset.fromMap(x))),
-      selectedAdditionalAsset: Set<ImmichAsset>.from(
-          map['selectedAdditionalAsset']?.map((x) => ImmichAsset.fromMap(x))),
-      isAlbumExist: map['isAlbumExist'] ?? false,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory AssetSelectionPageResult.fromJson(String source) =>
-      AssetSelectionPageResult.fromMap(json.decode(source));
-
   @override
   String toString() =>
       'AssetSelectionPageResult(selectedNewAsset: $selectedNewAsset, selectedAdditionalAsset: $selectedAdditionalAsset, isAlbumExist: $isAlbumExist)';
diff --git a/mobile/lib/modules/sharing/models/asset_selection_state.model.dart b/mobile/lib/modules/sharing/models/asset_selection_state.model.dart
index 7520bc2a59..6a0a9160a1 100644
--- a/mobile/lib/modules/sharing/models/asset_selection_state.model.dart
+++ b/mobile/lib/modules/sharing/models/asset_selection_state.model.dart
@@ -1,14 +1,12 @@
-import 'dart:convert';
-
 import 'package:collection/collection.dart';
 
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class AssetSelectionState {
   final Set<String> selectedMonths;
-  final Set<ImmichAsset> selectedNewAssetsForAlbum;
-  final Set<ImmichAsset> selectedAdditionalAssetsForAlbum;
-  final Set<ImmichAsset> selectedAssetsInAlbumViewer;
+  final Set<AssetResponseDto> selectedNewAssetsForAlbum;
+  final Set<AssetResponseDto> selectedAdditionalAssetsForAlbum;
+  final Set<AssetResponseDto> selectedAssetsInAlbumViewer;
   final bool isMultiselectEnable;
 
   /// Indicate the asset selection page is navigated from existing album
@@ -24,9 +22,9 @@ class AssetSelectionState {
 
   AssetSelectionState copyWith({
     Set<String>? selectedMonths,
-    Set<ImmichAsset>? selectedNewAssetsForAlbum,
-    Set<ImmichAsset>? selectedAdditionalAssetsForAlbum,
-    Set<ImmichAsset>? selectedAssetsInAlbumViewer,
+    Set<AssetResponseDto>? selectedNewAssetsForAlbum,
+    Set<AssetResponseDto>? selectedAdditionalAssetsForAlbum,
+    Set<AssetResponseDto>? selectedAssetsInAlbumViewer,
     bool? isMultiselectEnable,
     bool? isAlbumExist,
   }) {
@@ -43,49 +41,6 @@ class AssetSelectionState {
     );
   }
 
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll({'selectedMonths': selectedMonths.toList()});
-    result.addAll({
-      'selectedNewAssetsForAlbum':
-          selectedNewAssetsForAlbum.map((x) => x.toMap()).toList()
-    });
-    result.addAll({
-      'selectedAdditionalAssetsForAlbum':
-          selectedAdditionalAssetsForAlbum.map((x) => x.toMap()).toList()
-    });
-    result.addAll({
-      'selectedAssetsInAlbumViewer':
-          selectedAssetsInAlbumViewer.map((x) => x.toMap()).toList()
-    });
-    result.addAll({'isMultiselectEnable': isMultiselectEnable});
-    result.addAll({'isAlbumExist': isAlbumExist});
-
-    return result;
-  }
-
-  factory AssetSelectionState.fromMap(Map<String, dynamic> map) {
-    return AssetSelectionState(
-      selectedMonths: Set<String>.from(map['selectedMonths']),
-      selectedNewAssetsForAlbum: Set<ImmichAsset>.from(
-          map['selectedNewAssetsForAlbum']?.map((x) => ImmichAsset.fromMap(x))),
-      selectedAdditionalAssetsForAlbum: Set<ImmichAsset>.from(
-          map['selectedAdditionalAssetsForAlbum']
-              ?.map((x) => ImmichAsset.fromMap(x))),
-      selectedAssetsInAlbumViewer: Set<ImmichAsset>.from(
-          map['selectedAssetsInAlbumViewer']
-              ?.map((x) => ImmichAsset.fromMap(x))),
-      isMultiselectEnable: map['isMultiselectEnable'] ?? false,
-      isAlbumExist: map['isAlbumExist'] ?? false,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory AssetSelectionState.fromJson(String source) =>
-      AssetSelectionState.fromMap(json.decode(source));
-
   @override
   String toString() {
     return 'AssetSelectionState(selectedMonths: $selectedMonths, selectedNewAssetsForAlbum: $selectedNewAssetsForAlbum, selectedAdditionalAssetsForAlbum: $selectedAdditionalAssetsForAlbum, selectedAssetsInAlbumViewer: $selectedAssetsInAlbumViewer, isMultiselectEnable: $isMultiselectEnable, isAlbumExist: $isAlbumExist)';
@@ -99,10 +54,14 @@ class AssetSelectionState {
     return other is AssetSelectionState &&
         setEquals(other.selectedMonths, selectedMonths) &&
         setEquals(other.selectedNewAssetsForAlbum, selectedNewAssetsForAlbum) &&
-        setEquals(other.selectedAdditionalAssetsForAlbum,
-            selectedAdditionalAssetsForAlbum) &&
         setEquals(
-            other.selectedAssetsInAlbumViewer, selectedAssetsInAlbumViewer) &&
+          other.selectedAdditionalAssetsForAlbum,
+          selectedAdditionalAssetsForAlbum,
+        ) &&
+        setEquals(
+          other.selectedAssetsInAlbumViewer,
+          selectedAssetsInAlbumViewer,
+        ) &&
         other.isMultiselectEnable == isMultiselectEnable &&
         other.isAlbumExist == isAlbumExist;
   }
diff --git a/mobile/lib/modules/sharing/models/shared_album.model.dart b/mobile/lib/modules/sharing/models/shared_album.model.dart
deleted file mode 100644
index d8f1468927..0000000000
--- a/mobile/lib/modules/sharing/models/shared_album.model.dart
+++ /dev/null
@@ -1,117 +0,0 @@
-import 'dart:convert';
-
-import 'package:collection/collection.dart';
-
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:immich_mobile/shared/models/user.model.dart';
-
-class SharedAlbum {
-  final String id;
-  final String ownerId;
-  final String albumName;
-  final String createdAt;
-  final String? albumThumbnailAssetId;
-  final List<User> sharedUsers;
-  final List<ImmichAsset>? assets;
-
-  SharedAlbum({
-    required this.id,
-    required this.ownerId,
-    required this.albumName,
-    required this.createdAt,
-    required this.albumThumbnailAssetId,
-    required this.sharedUsers,
-    this.assets,
-  });
-
-  SharedAlbum copyWith({
-    String? id,
-    String? ownerId,
-    String? albumName,
-    String? createdAt,
-    String? albumThumbnailAssetId,
-    List<User>? sharedUsers,
-    List<ImmichAsset>? assets,
-  }) {
-    return SharedAlbum(
-      id: id ?? this.id,
-      ownerId: ownerId ?? this.ownerId,
-      albumName: albumName ?? this.albumName,
-      createdAt: createdAt ?? this.createdAt,
-      albumThumbnailAssetId:
-          albumThumbnailAssetId ?? this.albumThumbnailAssetId,
-      sharedUsers: sharedUsers ?? this.sharedUsers,
-      assets: assets ?? this.assets,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll({'id': id});
-    result.addAll({'ownerId': ownerId});
-    result.addAll({'albumName': albumName});
-    result.addAll({'createdAt': createdAt});
-    if (albumThumbnailAssetId != null) {
-      result.addAll({'albumThumbnailAssetId': albumThumbnailAssetId});
-    }
-    result.addAll({'sharedUsers': sharedUsers.map((x) => x.toMap()).toList()});
-    if (assets != null) {
-      result.addAll({'assets': assets!.map((x) => x.toMap()).toList()});
-    }
-
-    return result;
-  }
-
-  factory SharedAlbum.fromMap(Map<String, dynamic> map) {
-    return SharedAlbum(
-      id: map['id'] ?? '',
-      ownerId: map['ownerId'] ?? '',
-      albumName: map['albumName'] ?? '',
-      createdAt: map['createdAt'] ?? '',
-      albumThumbnailAssetId: map['albumThumbnailAssetId'],
-      sharedUsers:
-          List<User>.from(map['sharedUsers']?.map((x) => User.fromMap(x))),
-      assets: map['assets'] != null
-          ? List<ImmichAsset>.from(
-              map['assets']?.map((x) => ImmichAsset.fromMap(x)))
-          : null,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory SharedAlbum.fromJson(String source) =>
-      SharedAlbum.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'SharedAlbum(id: $id, ownerId: $ownerId, albumName: $albumName, createdAt: $createdAt, albumThumbnailAssetId: $albumThumbnailAssetId, sharedUsers: $sharedUsers, assets: $assets)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-    final listEquals = const DeepCollectionEquality().equals;
-
-    return other is SharedAlbum &&
-        other.id == id &&
-        other.ownerId == ownerId &&
-        other.albumName == albumName &&
-        other.createdAt == createdAt &&
-        other.albumThumbnailAssetId == albumThumbnailAssetId &&
-        listEquals(other.sharedUsers, sharedUsers) &&
-        listEquals(other.assets, assets);
-  }
-
-  @override
-  int get hashCode {
-    return id.hashCode ^
-        ownerId.hashCode ^
-        albumName.hashCode ^
-        createdAt.hashCode ^
-        albumThumbnailAssetId.hashCode ^
-        sharedUsers.hashCode ^
-        assets.hashCode;
-  }
-}
diff --git a/mobile/lib/modules/sharing/providers/album_title.provider.dart b/mobile/lib/modules/sharing/providers/album_title.provider.dart
index 5ccc82d401..126b3499a3 100644
--- a/mobile/lib/modules/sharing/providers/album_title.provider.dart
+++ b/mobile/lib/modules/sharing/providers/album_title.provider.dart
@@ -13,4 +13,5 @@ class AlbumTitleNotifier extends StateNotifier<String> {
 }
 
 final albumTitleProvider = StateNotifierProvider<AlbumTitleNotifier, String>(
-    (ref) => AlbumTitleNotifier());
+  (ref) => AlbumTitleNotifier(),
+);
diff --git a/mobile/lib/modules/sharing/providers/album_viewer.provider.dart b/mobile/lib/modules/sharing/providers/album_viewer.provider.dart
index 4e64b8eb5b..561f937102 100644
--- a/mobile/lib/modules/sharing/providers/album_viewer.provider.dart
+++ b/mobile/lib/modules/sharing/providers/album_viewer.provider.dart
@@ -30,7 +30,10 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
   }
 
   Future<bool> changeAlbumTitle(
-      String albumId, String ownerId, String newAlbumTitle) async {
+    String albumId,
+    String ownerId,
+    String newAlbumTitle,
+  ) async {
     SharedAlbumService service = ref.watch(sharedAlbumServiceProvider);
 
     bool isSuccess =
diff --git a/mobile/lib/modules/sharing/providers/asset_selection.provider.dart b/mobile/lib/modules/sharing/providers/asset_selection.provider.dart
index c87a59aa25..809e3d18c1 100644
--- a/mobile/lib/modules/sharing/providers/asset_selection.provider.dart
+++ b/mobile/lib/modules/sharing/providers/asset_selection.provider.dart
@@ -1,41 +1,46 @@
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/sharing/models/asset_selection_state.model.dart';
 
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
   AssetSelectionNotifier()
-      : super(AssetSelectionState(
-          selectedNewAssetsForAlbum: {},
-          selectedMonths: {},
-          selectedAdditionalAssetsForAlbum: {},
-          selectedAssetsInAlbumViewer: {},
-          isAlbumExist: false,
-          isMultiselectEnable: false,
-        ));
+      : super(
+          AssetSelectionState(
+            selectedNewAssetsForAlbum: {},
+            selectedMonths: {},
+            selectedAdditionalAssetsForAlbum: {},
+            selectedAssetsInAlbumViewer: {},
+            isAlbumExist: false,
+            isMultiselectEnable: false,
+          ),
+        );
 
   void setIsAlbumExist(bool isAlbumExist) {
     state = state.copyWith(isAlbumExist: isAlbumExist);
   }
 
   void removeAssetsInMonth(
-      String removedMonth, List<ImmichAsset> assetsInMonth) {
-    Set<ImmichAsset> currentAssetList = state.selectedNewAssetsForAlbum;
+    String removedMonth,
+    List<AssetResponseDto> assetsInMonth,
+  ) {
+    Set<AssetResponseDto> currentAssetList = state.selectedNewAssetsForAlbum;
     Set<String> currentMonthList = state.selectedMonths;
 
     currentMonthList
         .removeWhere((selectedMonth) => selectedMonth == removedMonth);
 
-    for (ImmichAsset asset in assetsInMonth) {
+    for (AssetResponseDto asset in assetsInMonth) {
       currentAssetList.removeWhere((e) => e.id == asset.id);
     }
 
     state = state.copyWith(
-        selectedNewAssetsForAlbum: currentAssetList,
-        selectedMonths: currentMonthList);
+      selectedNewAssetsForAlbum: currentAssetList,
+      selectedMonths: currentMonthList,
+    );
   }
 
-  void addAdditionalAssets(List<ImmichAsset> assets) {
+  void addAdditionalAssets(List<AssetResponseDto> assets) {
     state = state.copyWith(
       selectedAdditionalAssetsForAlbum: {
         ...state.selectedAdditionalAssetsForAlbum,
@@ -44,7 +49,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
     );
   }
 
-  void addAllAssetsInMonth(String month, List<ImmichAsset> assetsInMonth) {
+  void addAllAssetsInMonth(String month, List<AssetResponseDto> assetsInMonth) {
     state = state.copyWith(
       selectedMonths: {...state.selectedMonths, month},
       selectedNewAssetsForAlbum: {
@@ -54,7 +59,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
     );
   }
 
-  void addNewAssets(List<ImmichAsset> assets) {
+  void addNewAssets(List<AssetResponseDto> assets) {
     state = state.copyWith(
       selectedNewAssetsForAlbum: {
         ...state.selectedNewAssetsForAlbum,
@@ -63,20 +68,20 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
     );
   }
 
-  void removeSelectedNewAssets(List<ImmichAsset> assets) {
-    Set<ImmichAsset> currentList = state.selectedNewAssetsForAlbum;
+  void removeSelectedNewAssets(List<AssetResponseDto> assets) {
+    Set<AssetResponseDto> currentList = state.selectedNewAssetsForAlbum;
 
-    for (ImmichAsset asset in assets) {
+    for (AssetResponseDto asset in assets) {
       currentList.removeWhere((e) => e.id == asset.id);
     }
 
     state = state.copyWith(selectedNewAssetsForAlbum: currentList);
   }
 
-  void removeSelectedAdditionalAssets(List<ImmichAsset> assets) {
-    Set<ImmichAsset> currentList = state.selectedAdditionalAssetsForAlbum;
+  void removeSelectedAdditionalAssets(List<AssetResponseDto> assets) {
+    Set<AssetResponseDto> currentList = state.selectedAdditionalAssetsForAlbum;
 
-    for (ImmichAsset asset in assets) {
+    for (AssetResponseDto asset in assets) {
       currentList.removeWhere((e) => e.id == asset.id);
     }
 
@@ -104,7 +109,7 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
     );
   }
 
-  void addAssetsInAlbumViewer(List<ImmichAsset> assets) {
+  void addAssetsInAlbumViewer(List<AssetResponseDto> assets) {
     state = state.copyWith(
       selectedAssetsInAlbumViewer: {
         ...state.selectedAssetsInAlbumViewer,
@@ -113,10 +118,10 @@ class AssetSelectionNotifier extends StateNotifier<AssetSelectionState> {
     );
   }
 
-  void removeAssetsInAlbumViewer(List<ImmichAsset> assets) {
-    Set<ImmichAsset> currentList = state.selectedAssetsInAlbumViewer;
+  void removeAssetsInAlbumViewer(List<AssetResponseDto> assets) {
+    Set<AssetResponseDto> currentList = state.selectedAssetsInAlbumViewer;
 
-    for (ImmichAsset asset in assets) {
+    for (AssetResponseDto asset in assets) {
       currentList.removeWhere((e) => e.id == asset.id);
     }
 
diff --git a/mobile/lib/modules/sharing/providers/shared_album.provider.dart b/mobile/lib/modules/sharing/providers/shared_album.provider.dart
index efb3c090a7..792db47aa7 100644
--- a/mobile/lib/modules/sharing/providers/shared_album.provider.dart
+++ b/mobile/lib/modules/sharing/providers/shared_album.provider.dart
@@ -1,17 +1,19 @@
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
 import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
+import 'package:openapi/api.dart';
 
-class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> {
+class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
   SharedAlbumNotifier(this._sharedAlbumService) : super([]);
 
   final SharedAlbumService _sharedAlbumService;
 
   getAllSharedAlbums() async {
-    List<SharedAlbum> sharedAlbums =
+    List<AlbumResponseDto>? sharedAlbums =
         await _sharedAlbumService.getAllSharedAlbum();
 
-    state = sharedAlbums;
+    if (sharedAlbums != null) {
+      state = sharedAlbums;
+    }
   }
 
   Future<bool> deleteAlbum(String albumId) async {
@@ -37,7 +39,9 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> {
   }
 
   Future<bool> removeAssetFromAlbum(
-      String albumId, List<String> assetIds) async {
+    String albumId,
+    List<String> assetIds,
+  ) async {
     var res = await _sharedAlbumService.removeAssetFromAlbum(albumId, assetIds);
 
     if (res) {
@@ -49,12 +53,12 @@ class SharedAlbumNotifier extends StateNotifier<List<SharedAlbum>> {
 }
 
 final sharedAlbumProvider =
-    StateNotifierProvider<SharedAlbumNotifier, List<SharedAlbum>>((ref) {
+    StateNotifierProvider<SharedAlbumNotifier, List<AlbumResponseDto>>((ref) {
   return SharedAlbumNotifier(ref.watch(sharedAlbumServiceProvider));
 });
 
 final sharedAlbumDetailProvider = FutureProvider.autoDispose
-    .family<SharedAlbum, String>((ref, albumId) async {
+    .family<AlbumResponseDto?, String>((ref, albumId) async {
   final SharedAlbumService sharedAlbumService =
       ref.watch(sharedAlbumServiceProvider);
 
diff --git a/mobile/lib/modules/sharing/providers/suggested_shared_users.provider.dart b/mobile/lib/modules/sharing/providers/suggested_shared_users.provider.dart
index 8ddc122b26..0c4a2a3503 100644
--- a/mobile/lib/modules/sharing/providers/suggested_shared_users.provider.dart
+++ b/mobile/lib/modules/sharing/providers/suggested_shared_users.provider.dart
@@ -1,10 +1,10 @@
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/shared/models/user.model.dart';
 import 'package:immich_mobile/shared/services/user.service.dart';
+import 'package:openapi/api.dart';
 
 final suggestedSharedUsersProvider =
-    FutureProvider.autoDispose<List<User>>((ref) async {
+    FutureProvider.autoDispose<List<UserResponseDto>>((ref) async {
   UserService userService = ref.watch(userServiceProvider);
 
-  return await userService.getAllUsersInfo();
+  return await userService.getAllUsersInfo(isAll: false) ?? [];
 });
diff --git a/mobile/lib/modules/sharing/services/shared_album.service.dart b/mobile/lib/modules/sharing/services/shared_album.service.dart
index 9183dcb8d8..92a995fb91 100644
--- a/mobile/lib/modules/sharing/services/shared_album.service.dart
+++ b/mobile/lib/modules/sharing/services/shared_album.service.dart
@@ -1,73 +1,69 @@
 import 'dart:async';
-import 'dart:convert';
 
-import 'package:dio/dio.dart';
 import 'package:flutter/foundation.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
+import 'package:openapi/api.dart';
 
-final sharedAlbumServiceProvider =
-    Provider((ref) => SharedAlbumService(ref.watch(networkServiceProvider)));
+final sharedAlbumServiceProvider = Provider(
+  (ref) => SharedAlbumService(
+    ref.watch(apiServiceProvider),
+  ),
+);
 
 class SharedAlbumService {
-  final NetworkService _networkService;
-  SharedAlbumService(this._networkService);
+  final ApiService _apiService;
+  SharedAlbumService(this._apiService);
 
-  Future<List<SharedAlbum>> getAllSharedAlbum() async {
+  Future<List<AlbumResponseDto>?> getAllSharedAlbum() async {
     try {
-      var res = await _networkService.getRequest(url: 'album?shared=true');
-      List<dynamic> decodedData = jsonDecode(res.toString());
-      List<SharedAlbum> result =
-          List.from(decodedData.map((e) => SharedAlbum.fromMap(e)));
-
-      return result;
+      return await _apiService.albumApi.getAllAlbums(shared: true);
     } catch (e) {
       debugPrint("Error getAllSharedAlbum  ${e.toString()}");
+      return null;
     }
-
-    return [];
   }
 
-  Future<bool> createSharedAlbum(String albumName, Set<ImmichAsset> assets,
-      List<String> sharedUserIds) async {
+  Future<bool> createSharedAlbum(
+    String albumName,
+    Set<AssetResponseDto> assets,
+    List<String> sharedUserIds,
+  ) async {
     try {
-      var res = await _networkService.postRequest(url: 'album', data: {
-        "albumName": albumName,
-        "sharedWithUserIds": sharedUserIds,
-        "assetIds": assets.map((asset) => asset.id).toList(),
-      });
+      _apiService.albumApi.createAlbum(
+        CreateAlbumDto(
+          albumName: albumName,
+          assetIds: assets.map((asset) => asset.id).toList(),
+          sharedWithUserIds: sharedUserIds,
+        ),
+      );
 
-      return res != null;
+      return true;
     } catch (e) {
       debugPrint("Error createSharedAlbum  ${e.toString()}");
       return false;
     }
   }
 
-  Future<SharedAlbum> getAlbumDetail(String albumId) async {
+  Future<AlbumResponseDto?> getAlbumDetail(String albumId) async {
     try {
-      var res = await _networkService.getRequest(url: 'album/$albumId');
-      dynamic decodedData = jsonDecode(res.toString());
-      SharedAlbum result = SharedAlbum.fromMap(decodedData);
-
-      return result;
+      return await _apiService.albumApi.getAlbumInfo(albumId);
     } catch (e) {
-      throw Exception('Error getAllSharedAlbum  ${e.toString()}');
+      debugPrint('Error [getAlbumDetail] ${e.toString()}');
+      return null;
     }
   }
 
   Future<bool> addAdditionalAssetToAlbum(
-      Set<ImmichAsset> assets, String albumId) async {
+    Set<AssetResponseDto> assets,
+    String albumId,
+  ) async {
     try {
-      var res =
-          await _networkService.putRequest(url: 'album/$albumId/assets', data: {
-        "albumId": albumId,
-        "assetIds": assets.map((asset) => asset.id).toList(),
-      });
-
-      return res != null;
+      var result = await _apiService.albumApi.addAssetsToAlbum(
+        albumId,
+        AddAssetsDto(assetIds: assets.map((asset) => asset.id).toList()),
+      );
+      return result != null;
     } catch (e) {
       debugPrint("Error addAdditionalAssetToAlbum  ${e.toString()}");
       return false;
@@ -75,14 +71,16 @@ class SharedAlbumService {
   }
 
   Future<bool> addAdditionalUserToAlbum(
-      List<String> sharedUserIds, String albumId) async {
+    List<String> sharedUserIds,
+    String albumId,
+  ) async {
     try {
-      var res =
-          await _networkService.putRequest(url: 'album/$albumId/users', data: {
-        "sharedUserIds": sharedUserIds,
-      });
+      var result = await _apiService.albumApi.addUsersToAlbum(
+        albumId,
+        AddUsersDto(sharedUserIds: sharedUserIds),
+      );
 
-      return res != null;
+      return result != null;
     } catch (e) {
       debugPrint("Error addAdditionalUserToAlbum  ${e.toString()}");
       return false;
@@ -91,12 +89,7 @@ class SharedAlbumService {
 
   Future<bool> deleteAlbum(String albumId) async {
     try {
-      Response res = await _networkService.deleteRequest(url: 'album/$albumId');
-
-      if (res.statusCode != 200) {
-        return false;
-      }
-
+      await _apiService.albumApi.deleteAlbum(albumId);
       return true;
     } catch (e) {
       debugPrint("Error deleteAlbum  ${e.toString()}");
@@ -106,12 +99,7 @@ class SharedAlbumService {
 
   Future<bool> leaveAlbum(String albumId) async {
     try {
-      Response res =
-          await _networkService.deleteRequest(url: 'album/$albumId/user/me');
-
-      if (res.statusCode != 200) {
-        return false;
-      }
+      await _apiService.albumApi.removeUserFromAlbum(albumId, "me");
 
       return true;
     } catch (e) {
@@ -121,16 +109,14 @@ class SharedAlbumService {
   }
 
   Future<bool> removeAssetFromAlbum(
-      String albumId, List<String> assetIds) async {
+    String albumId,
+    List<String> assetIds,
+  ) async {
     try {
-      Response res = await _networkService
-          .deleteRequest(url: 'album/$albumId/assets', data: {
-        "assetIds": assetIds,
-      });
-
-      if (res.statusCode != 200) {
-        return false;
-      }
+      await _apiService.albumApi.removeAssetFromAlbum(
+        albumId,
+        RemoveAssetsDto(assetIds: assetIds),
+      );
 
       return true;
     } catch (e) {
@@ -140,17 +126,18 @@ class SharedAlbumService {
   }
 
   Future<bool> changeTitleAlbum(
-      String albumId, String ownerId, String newAlbumTitle) async {
+    String albumId,
+    String ownerId,
+    String newAlbumTitle,
+  ) async {
     try {
-      Response res =
-          await _networkService.patchRequest(url: 'album/$albumId/', data: {
-        "ownerId": ownerId,
-        "albumName": newAlbumTitle,
-      });
-
-      if (res.statusCode != 200) {
-        return false;
-      }
+      await _apiService.albumApi.updateAlbumInfo(
+        albumId,
+        UpdateAlbumDto(
+          ownerId: ownerId,
+          albumName: newAlbumTitle,
+        ),
+      );
 
       return true;
     } catch (e) {
diff --git a/mobile/lib/modules/sharing/ui/album_action_outlined_button.dart b/mobile/lib/modules/sharing/ui/album_action_outlined_button.dart
index 3a4a351cfb..fdfab59768 100644
--- a/mobile/lib/modules/sharing/ui/album_action_outlined_button.dart
+++ b/mobile/lib/modules/sharing/ui/album_action_outlined_button.dart
@@ -5,12 +5,12 @@ class AlbumActionOutlinedButton extends StatelessWidget {
   final String labelText;
   final IconData iconData;
 
-  const AlbumActionOutlinedButton(
-      {Key? key,
-      this.onPressed,
-      required this.labelText,
-      required this.iconData})
-      : super(key: key);
+  const AlbumActionOutlinedButton({
+    Key? key,
+    this.onPressed,
+    required this.labelText,
+    required this.iconData,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -31,7 +31,10 @@ class AlbumActionOutlinedButton extends StatelessWidget {
         label: Text(
           labelText,
           style: const TextStyle(
-              fontSize: 12, fontWeight: FontWeight.bold, color: Colors.black87),
+            fontSize: 12,
+            fontWeight: FontWeight.bold,
+            color: Colors.black87,
+          ),
         ),
         onPressed: onPressed,
       ),
diff --git a/mobile/lib/modules/sharing/ui/album_title_text_field.dart b/mobile/lib/modules/sharing/ui/album_title_text_field.dart
index 1556c87fad..61dbad01a6 100644
--- a/mobile/lib/modules/sharing/ui/album_title_text_field.dart
+++ b/mobile/lib/modules/sharing/ui/album_title_text_field.dart
@@ -31,7 +31,10 @@ class AlbumTitleTextField extends ConsumerWidget {
       },
       focusNode: albumTitleTextFieldFocusNode,
       style: TextStyle(
-          fontSize: 28, color: Colors.grey[700], fontWeight: FontWeight.bold),
+        fontSize: 28,
+        color: Colors.grey[700],
+        fontWeight: FontWeight.bold,
+      ),
       controller: albumTitleController,
       onTap: () {
         isAlbumTitleTextFieldFocus.value = true;
diff --git a/mobile/lib/modules/sharing/ui/album_viewer_appbar.dart b/mobile/lib/modules/sharing/ui/album_viewer_appbar.dart
index bcb0690a24..dd85021e5b 100644
--- a/mobile/lib/modules/sharing/ui/album_viewer_appbar.dart
+++ b/mobile/lib/modules/sharing/ui/album_viewer_appbar.dart
@@ -4,24 +4,24 @@ import 'package:flutter/material.dart';
 import 'package:fluttertoast/fluttertoast.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/immich_colors.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
 import 'package:immich_mobile/modules/sharing/providers/album_viewer.provider.dart';
 import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
 import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/shared/ui/immich_toast.dart';
 import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
+import 'package:openapi/api.dart';
 
 class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
   const AlbumViewerAppbar({
     Key? key,
-    required AsyncValue<SharedAlbum> albumInfo,
+    required AsyncValue<AlbumResponseDto?> albumInfo,
     required this.userId,
     required this.albumId,
   })  : _albumInfo = albumInfo,
         super(key: key);
 
-  final AsyncValue<SharedAlbum> _albumInfo;
+  final AsyncValue<AlbumResponseDto?> _albumInfo;
   final String userId;
   final String albumId;
 
@@ -105,7 +105,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
 
     _buildBottomSheetActionButton() {
       if (isMultiSelectionEnable) {
-        if (_albumInfo.asData?.value.ownerId == userId) {
+        if (_albumInfo.asData?.value?.ownerId == userId) {
           return ListTile(
             leading: const Icon(Icons.delete_sweep_rounded),
             title: const Text(
@@ -118,7 +118,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
           return const SizedBox();
         }
       } else {
-        if (_albumInfo.asData?.value.ownerId == userId) {
+        if (_albumInfo.asData?.value?.ownerId == userId) {
           return ListTile(
             leading: const Icon(Icons.delete_forever_rounded),
             title: const Text(
diff --git a/mobile/lib/modules/sharing/ui/album_viewer_editable_title.dart b/mobile/lib/modules/sharing/ui/album_viewer_editable_title.dart
index 52d0938680..79f87d5b27 100644
--- a/mobile/lib/modules/sharing/ui/album_viewer_editable_title.dart
+++ b/mobile/lib/modules/sharing/ui/album_viewer_editable_title.dart
@@ -2,15 +2,17 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
 import 'package:immich_mobile/modules/sharing/providers/album_viewer.provider.dart';
+import 'package:openapi/api.dart';
 
 class AlbumViewerEditableTitle extends HookConsumerWidget {
-  final SharedAlbum albumInfo;
+  final AlbumResponseDto albumInfo;
   final FocusNode titleFocusNode;
-  const AlbumViewerEditableTitle(
-      {Key? key, required this.albumInfo, required this.titleFocusNode})
-      : super(key: key);
+  const AlbumViewerEditableTitle({
+    Key? key,
+    required this.albumInfo,
+    required this.titleFocusNode,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
@@ -24,12 +26,15 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
       }
     }
 
-    useEffect(() {
-      titleFocusNode.addListener(onFocusModeChange);
-      return () {
-        titleFocusNode.removeListener(onFocusModeChange);
-      };
-    }, []);
+    useEffect(
+      () {
+        titleFocusNode.addListener(onFocusModeChange);
+        return () {
+          titleFocusNode.removeListener(onFocusModeChange);
+        };
+      },
+      [],
+    );
 
     return TextField(
       onChanged: (value) {
diff --git a/mobile/lib/modules/sharing/ui/album_viewer_thumbnail.dart b/mobile/lib/modules/sharing/ui/album_viewer_thumbnail.dart
index 95a2b1bf78..c5f3a6bd99 100644
--- a/mobile/lib/modules/sharing/ui/album_viewer_thumbnail.dart
+++ b/mobile/lib/modules/sharing/ui/album_viewer_thumbnail.dart
@@ -7,11 +7,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:immich_mobile/routing/router.dart';
+import 'package:openapi/api.dart';
 
 class AlbumViewerThumbnail extends HookConsumerWidget {
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
 
   const AlbumViewerThumbnail({Key? key, required this.asset}) : super(key: key);
 
@@ -20,7 +20,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
     final cacheKey = useState(1);
     var box = Hive.box(userInfoBox);
     var thumbnailRequestUrl =
-        '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
+        '${box.get(serverEndpointKey)}/asset/thumbnail/${asset.id}';
     var deviceId = ref.watch(authenticationProvider).deviceId;
     final selectedAssetsInAlbumViewer =
         ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
@@ -28,7 +28,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
         ref.watch(assetSelectionProvider).isMultiselectEnable;
 
     _viewAsset() {
-      if (asset.type == 'IMAGE') {
+      if (asset.type == AssetTypeEnum.IMAGE) {
         AutoRouter.of(context).push(
           ImageViewerRoute(
             imageUrl:
@@ -41,9 +41,10 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
       } else {
         AutoRouter.of(context).push(
           VideoViewerRoute(
-              videoUrl:
-                  '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
-              asset: asset),
+            videoUrl:
+                '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
+            asset: asset,
+          ),
         );
       }
     }
@@ -170,16 +171,13 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
     return GestureDetector(
       onTap: isMultiSelectionEnable ? _handleSelectionGesture : _viewAsset,
       onLongPress: _enableMultiSelection,
-      child: Hero(
-        tag: asset.id,
-        child: Stack(
-          children: [
-            _buildThumbnailImage(),
-            _buildAssetStoreLocationIcon(),
-            if (asset.type != 'IMAGE') _buildVideoLabel(),
-            if (isMultiSelectionEnable) _buildAssetSelectionIcon(),
-          ],
-        ),
+      child: Stack(
+        children: [
+          _buildThumbnailImage(),
+          _buildAssetStoreLocationIcon(),
+          if (asset.type != AssetTypeEnum.IMAGE) _buildVideoLabel(),
+          if (isMultiSelectionEnable) _buildAssetSelectionIcon(),
+        ],
       ),
     );
   }
diff --git a/mobile/lib/modules/sharing/ui/asset_grid_by_month.dart b/mobile/lib/modules/sharing/ui/asset_grid_by_month.dart
index 8d719bac57..3586487621 100644
--- a/mobile/lib/modules/sharing/ui/asset_grid_by_month.dart
+++ b/mobile/lib/modules/sharing/ui/asset_grid_by_month.dart
@@ -1,10 +1,10 @@
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/sharing/ui/selection_thumbnail_image.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class AssetGridByMonth extends HookConsumerWidget {
-  final List<ImmichAsset> assetGroup;
+  final List<AssetResponseDto> assetGroup;
   const AssetGridByMonth({Key? key, required this.assetGroup})
       : super(key: key);
   @override
diff --git a/mobile/lib/modules/sharing/ui/month_group_title.dart b/mobile/lib/modules/sharing/ui/month_group_title.dart
index 71e160664c..ba766721aa 100644
--- a/mobile/lib/modules/sharing/ui/month_group_title.dart
+++ b/mobile/lib/modules/sharing/ui/month_group_title.dart
@@ -2,15 +2,17 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class MonthGroupTitle extends HookConsumerWidget {
   final String month;
-  final List<ImmichAsset> assetGroup;
+  final List<AssetResponseDto> assetGroup;
 
-  const MonthGroupTitle(
-      {Key? key, required this.month, required this.assetGroup})
-      : super(key: key);
+  const MonthGroupTitle({
+    Key? key,
+    required this.month,
+    required this.assetGroup,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
@@ -75,7 +77,11 @@ class MonthGroupTitle extends HookConsumerWidget {
     return SliverToBoxAdapter(
       child: Padding(
         padding: const EdgeInsets.only(
-            top: 29.0, bottom: 29.0, left: 14.0, right: 8.0),
+          top: 29.0,
+          bottom: 29.0,
+          left: 14.0,
+          right: 8.0,
+        ),
         child: Row(
           children: [
             GestureDetector(
diff --git a/mobile/lib/modules/sharing/ui/selection_thumbnail_image.dart b/mobile/lib/modules/sharing/ui/selection_thumbnail_image.dart
index 13e6ce2035..24be821066 100644
--- a/mobile/lib/modules/sharing/ui/selection_thumbnail_image.dart
+++ b/mobile/lib/modules/sharing/ui/selection_thumbnail_image.dart
@@ -5,10 +5,10 @@ import 'package:hive_flutter/hive_flutter.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class SelectionThumbnailImage extends HookConsumerWidget {
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
 
   const SelectionThumbnailImage({Key? key, required this.asset})
       : super(key: key);
@@ -18,14 +18,14 @@ class SelectionThumbnailImage extends HookConsumerWidget {
     final cacheKey = useState(1);
     var box = Hive.box(userInfoBox);
     var thumbnailRequestUrl =
-        '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
+        '${box.get(serverEndpointKey)}/asset/thumbnail/${asset.id}';
     var selectedAsset =
         ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
     var newAssetsForAlbum =
         ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
     var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
 
-    Widget _buildSelectionIcon(ImmichAsset asset) {
+    Widget _buildSelectionIcon(AssetResponseDto asset) {
       if (selectedAsset.contains(asset) && !isAlbumExist) {
         return Icon(
           Icons.check_circle,
@@ -103,7 +103,7 @@ class SelectionThumbnailImage extends HookConsumerWidget {
               cacheKey: "${asset.id}-${cacheKey.value}",
               width: 150,
               height: 150,
-              memCacheHeight: asset.type == 'IMAGE' ? 150 : 150,
+              memCacheHeight: asset.type == AssetTypeEnum.IMAGE ? 150 : 150,
               fit: BoxFit.cover,
               imageUrl: thumbnailRequestUrl,
               httpHeaders: {
@@ -131,14 +131,14 @@ class SelectionThumbnailImage extends HookConsumerWidget {
               child: _buildSelectionIcon(asset),
             ),
           ),
-          if (asset.type != 'IMAGE')
+          if (asset.type != AssetTypeEnum.IMAGE)
             Positioned(
               bottom: 5,
               right: 5,
               child: Row(
                 children: [
                   Text(
-                    '${asset.duration?.substring(0, 7)}',
+                    asset.duration.substring(0, 7),
                     style: const TextStyle(
                       color: Colors.white,
                       fontSize: 10,
diff --git a/mobile/lib/modules/sharing/ui/shared_album_thumbnail_image.dart b/mobile/lib/modules/sharing/ui/shared_album_thumbnail_image.dart
index b6b75d65ab..d62f1e097b 100644
--- a/mobile/lib/modules/sharing/ui/shared_album_thumbnail_image.dart
+++ b/mobile/lib/modules/sharing/ui/shared_album_thumbnail_image.dart
@@ -4,10 +4,10 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hive_flutter/hive_flutter.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
+import 'package:openapi/api.dart';
 
 class SharedAlbumThumbnailImage extends HookConsumerWidget {
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
 
   const SharedAlbumThumbnailImage({Key? key, required this.asset})
       : super(key: key);
@@ -18,7 +18,7 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
 
     var box = Hive.box(userInfoBox);
     var thumbnailRequestUrl =
-        '${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=true';
+        '${box.get(serverEndpointKey)}/asset/thumbnail/${asset.id}';
 
     return GestureDetector(
       onTap: () {
@@ -30,7 +30,7 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
             cacheKey: "${asset.id}-${cacheKey.value}",
             width: 500,
             height: 500,
-            memCacheHeight: asset.type == 'IMAGE' ? 500 : 500,
+            memCacheHeight: 500,
             fit: BoxFit.cover,
             imageUrl: thumbnailRequestUrl,
             httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
diff --git a/mobile/lib/modules/sharing/ui/sharing_sliver_appbar.dart b/mobile/lib/modules/sharing/ui/sharing_sliver_appbar.dart
index 32adf2138c..e39bcf115e 100644
--- a/mobile/lib/modules/sharing/ui/sharing_sliver_appbar.dart
+++ b/mobile/lib/modules/sharing/ui/sharing_sliver_appbar.dart
@@ -40,7 +40,8 @@ class SharingSliverAppBar extends StatelessWidget {
                   child: TextButton.icon(
                     style: ButtonStyle(
                       backgroundColor: MaterialStateProperty.all(
-                          Theme.of(context).primaryColor.withAlpha(20)),
+                        Theme.of(context).primaryColor.withAlpha(20),
+                      ),
                       // foregroundColor: MaterialStateProperty.all(Colors.white),
                     ),
                     onPressed: () {
@@ -65,7 +66,8 @@ class SharingSliverAppBar extends StatelessWidget {
                   child: TextButton.icon(
                     style: ButtonStyle(
                       backgroundColor: MaterialStateProperty.all(
-                          Theme.of(context).primaryColor.withAlpha(20)),
+                        Theme.of(context).primaryColor.withAlpha(20),
+                      ),
                       // foregroundColor: MaterialStateProperty.all(Colors.white),
                     ),
                     onPressed: null,
diff --git a/mobile/lib/modules/sharing/views/album_viewer_page.dart b/mobile/lib/modules/sharing/views/album_viewer_page.dart
index 1f26a643ec..c10268d677 100644
--- a/mobile/lib/modules/sharing/views/album_viewer_page.dart
+++ b/mobile/lib/modules/sharing/views/album_viewer_page.dart
@@ -7,7 +7,6 @@ import 'package:immich_mobile/constants/immich_colors.dart';
 import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
 import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
 import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
 import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
 import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
@@ -19,7 +18,7 @@ import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
 import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
 import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
-import 'package:intl/intl.dart';
+import 'package:openapi/api.dart';
 
 class AlbumViewerPage extends HookConsumerWidget {
   final String albumId;
@@ -30,18 +29,18 @@ class AlbumViewerPage extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     FocusNode titleFocusNode = useFocusNode();
     ScrollController scrollController = useScrollController();
-    AsyncValue<SharedAlbum> albumInfo =
+    AsyncValue<AlbumResponseDto?> albumInfo =
         ref.watch(sharedAlbumDetailProvider(albumId));
 
     final userId = ref.watch(authenticationProvider).userId;
 
     /// Find out if the assets in album exist on the device
     /// If they exist, add to selected asset state to show they are already selected.
-    void _onAddPhotosPressed(SharedAlbum albumInfo) async {
-      if (albumInfo.assets?.isNotEmpty == true) {
+    void _onAddPhotosPressed(AlbumResponseDto albumInfo) async {
+      if (albumInfo.assets.isNotEmpty == true) {
         ref
             .watch(assetSelectionProvider.notifier)
-            .addNewAssets(albumInfo.assets!.toList());
+            .addNewAssets(albumInfo.assets.toList());
       }
 
       ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(true);
@@ -57,7 +56,9 @@ class AlbumViewerPage extends HookConsumerWidget {
           var isSuccess = await ref
               .watch(sharedAlbumServiceProvider)
               .addAdditionalAssetToAlbum(
-                  returnPayload.selectedAdditionalAsset, albumId);
+                returnPayload.selectedAdditionalAsset,
+                albumId,
+              );
 
           if (isSuccess) {
             ref.refresh(sharedAlbumDetailProvider(albumId));
@@ -72,10 +73,11 @@ class AlbumViewerPage extends HookConsumerWidget {
       }
     }
 
-    void _onAddUsersPressed(SharedAlbum albumInfo) async {
-      List<String>? sharedUserIds = await AutoRouter.of(context)
-          .push<List<String>?>(
-              SelectAdditionalUserForSharingRoute(albumInfo: albumInfo));
+    void _onAddUsersPressed(AlbumResponseDto albumInfo) async {
+      List<String>? sharedUserIds =
+          await AutoRouter.of(context).push<List<String>?>(
+        SelectAdditionalUserForSharingRoute(albumInfo: albumInfo),
+      );
 
       if (sharedUserIds != null) {
         ImmichLoadingOverlayController.appLoader.show();
@@ -92,7 +94,7 @@ class AlbumViewerPage extends HookConsumerWidget {
       }
     }
 
-    Widget _buildTitle(SharedAlbum albumInfo) {
+    Widget _buildTitle(AlbumResponseDto albumInfo) {
       return Padding(
         padding: const EdgeInsets.only(left: 8, right: 8, top: 16),
         child: userId == albumInfo.ownerId
@@ -102,19 +104,24 @@ class AlbumViewerPage extends HookConsumerWidget {
               )
             : Padding(
                 padding: const EdgeInsets.only(left: 8.0),
-                child: Text(albumInfo.albumName,
-                    style: const TextStyle(
-                        fontSize: 24, fontWeight: FontWeight.bold)),
+                child: Text(
+                  albumInfo.albumName,
+                  style: const TextStyle(
+                    fontSize: 24,
+                    fontWeight: FontWeight.bold,
+                  ),
+                ),
               ),
       );
     }
 
-    Widget _buildAlbumDateRange(SharedAlbum albumInfo) {
+    Widget _buildAlbumDateRange(AlbumResponseDto albumInfo) {
       String startDate = "";
       DateTime parsedStartDate =
-          DateTime.parse(albumInfo.assets!.first.createdAt);
+          DateTime.parse(albumInfo.assets.first.createdAt);
       DateTime parsedEndDate = DateTime.parse(
-          albumInfo.assets?.last.createdAt ?? '11111111'); //Need default.
+        albumInfo.assets.last.createdAt,
+      ); //Need default.
 
       if (parsedStartDate.year == parsedEndDate.year) {
         startDate = DateFormat('LLL d').format(parsedStartDate);
@@ -129,18 +136,21 @@ class AlbumViewerPage extends HookConsumerWidget {
         child: Text(
           "$startDate-$endDate",
           style: const TextStyle(
-              fontSize: 14, fontWeight: FontWeight.bold, color: Colors.grey),
+            fontSize: 14,
+            fontWeight: FontWeight.bold,
+            color: Colors.grey,
+          ),
         ),
       );
     }
 
-    Widget _buildHeader(SharedAlbum albumInfo) {
+    Widget _buildHeader(AlbumResponseDto albumInfo) {
       return SliverToBoxAdapter(
         child: Column(
           crossAxisAlignment: CrossAxisAlignment.start,
           children: [
             _buildTitle(albumInfo),
-            if (albumInfo.assets?.isNotEmpty == true)
+            if (albumInfo.assets.isNotEmpty == true)
               _buildAlbumDateRange(albumInfo),
             SizedBox(
               height: 60,
@@ -172,8 +182,8 @@ class AlbumViewerPage extends HookConsumerWidget {
       );
     }
 
-    Widget _buildImageGrid(SharedAlbum albumInfo) {
-      if (albumInfo.assets?.isNotEmpty == true) {
+    Widget _buildImageGrid(AlbumResponseDto albumInfo) {
+      if (albumInfo.assets.isNotEmpty) {
         return SliverPadding(
           padding: const EdgeInsets.only(top: 10.0),
           sliver: SliverGrid(
@@ -184,9 +194,9 @@ class AlbumViewerPage extends HookConsumerWidget {
             ),
             delegate: SliverChildBuilderDelegate(
               (BuildContext context, int index) {
-                return AlbumViewerThumbnail(asset: albumInfo.assets![index]);
+                return AlbumViewerThumbnail(asset: albumInfo.assets[index]);
               },
-              childCount: albumInfo.assets?.length,
+              childCount: albumInfo.assets.length,
             ),
           ),
         );
@@ -194,7 +204,7 @@ class AlbumViewerPage extends HookConsumerWidget {
       return const SliverToBoxAdapter();
     }
 
-    Widget _buildControlButton(SharedAlbum albumInfo) {
+    Widget _buildControlButton(AlbumResponseDto albumInfo) {
       return Padding(
         padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
         child: SizedBox(
@@ -219,7 +229,7 @@ class AlbumViewerPage extends HookConsumerWidget {
       );
     }
 
-    Widget _buildBody(SharedAlbum albumInfo) {
+    Widget _buildBody(AlbumResponseDto albumInfo) {
       return GestureDetector(
         onTap: () {
           titleFocusNode.unfocus();
@@ -252,9 +262,16 @@ class AlbumViewerPage extends HookConsumerWidget {
 
     return Scaffold(
       appBar: AlbumViewerAppbar(
-          albumInfo: albumInfo, userId: userId, albumId: albumId),
+        albumInfo: albumInfo,
+        userId: userId,
+        albumId: albumId,
+      ),
       body: albumInfo.when(
-        data: (albumInfo) => _buildBody(albumInfo),
+        data: (albumInfo) => albumInfo != null
+            ? _buildBody(albumInfo)
+            : const Center(
+                child: CircularProgressIndicator(),
+              ),
         error: (e, _) => Center(child: Text("Error loading album info $e")),
         loading: () => const Center(
           child: ImmichLoadingIndicator(),
diff --git a/mobile/lib/modules/sharing/views/create_shared_album_page.dart b/mobile/lib/modules/sharing/views/create_shared_album_page.dart
index 2da6eae0a9..6cc6b1faaf 100644
--- a/mobile/lib/modules/sharing/views/create_shared_album_page.dart
+++ b/mobile/lib/modules/sharing/views/create_shared_album_page.dart
@@ -56,10 +56,11 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
           left: 10,
         ),
         child: AlbumTitleTextField(
-            isAlbumTitleEmpty: isAlbumTitleEmpty,
-            albumTitleTextFieldFocusNode: albumTitleTextFieldFocusNode,
-            albumTitleController: albumTitleController,
-            isAlbumTitleTextFieldFocus: isAlbumTitleTextFieldFocus),
+          isAlbumTitleEmpty: isAlbumTitleEmpty,
+          albumTitleTextFieldFocusNode: albumTitleTextFieldFocusNode,
+          albumTitleController: albumTitleController,
+          isAlbumTitleTextFieldFocus: isAlbumTitleTextFieldFocus,
+        ),
       );
     }
 
@@ -67,8 +68,8 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
       if (selectedAssets.isEmpty) {
         return SliverToBoxAdapter(
           child: Padding(
-            padding: EdgeInsets.only(top: 200, left: 18),
-            child: Text(
+            padding: const EdgeInsets.only(top: 200, left: 18),
+            child: const Text(
               'create_shared_album_page_share_add_assets',
               style: TextStyle(fontSize: 12),
             ).tr(),
@@ -86,13 +87,16 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
             padding: const EdgeInsets.only(top: 16, left: 18, right: 18),
             child: OutlinedButton.icon(
               style: OutlinedButton.styleFrom(
-                  alignment: Alignment.centerLeft,
-                  padding:
-                      const EdgeInsets.symmetric(vertical: 22, horizontal: 16),
-                  side: const BorderSide(
-                      color: Color.fromARGB(255, 206, 206, 206)),
-                  shape: RoundedRectangleBorder(
-                      borderRadius: BorderRadius.circular(5))),
+                alignment: Alignment.centerLeft,
+                padding:
+                    const EdgeInsets.symmetric(vertical: 22, horizontal: 16),
+                side: const BorderSide(
+                  color: Color.fromARGB(255, 206, 206, 206),
+                ),
+                shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(5),
+                ),
+              ),
               onPressed: _onSelectPhotosButtonPressed,
               icon: const Icon(Icons.add_rounded),
               label: Padding(
@@ -100,9 +104,10 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
                 child: Text(
                   'create_shared_album_page_share_select_photos',
                   style: TextStyle(
-                      fontSize: 16,
-                      color: Colors.grey[700],
-                      fontWeight: FontWeight.bold),
+                    fontSize: 16,
+                    color: Colors.grey[700],
+                    fontWeight: FontWeight.bold,
+                  ),
                 ).tr(),
               ),
             ),
@@ -147,7 +152,8 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
                 return GestureDetector(
                   onTap: _onBackgroundTapped,
                   child: SharedAlbumThumbnailImage(
-                      asset: selectedAssets.toList()[index]),
+                    asset: selectedAssets.toList()[index],
+                  ),
                 );
               },
               childCount: selectedAssets.length,
@@ -160,58 +166,60 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
     }
 
     return Scaffold(
-        appBar: AppBar(
-          elevation: 0,
-          centerTitle: false,
-          leading: IconButton(
-              onPressed: () {
-                ref.watch(assetSelectionProvider.notifier).removeAll();
-                AutoRouter.of(context).pop();
-              },
-              icon: const Icon(Icons.close_rounded)),
-          title: const Text(
-            'share_create_album',
-            style: TextStyle(color: Colors.black),
-          ).tr(),
-          actions: [
-            TextButton(
-              onPressed: albumTitleController.text.isNotEmpty
-                  ? _showSelectUserPage
-                  : null,
-              child: Text(
-                'create_shared_album_page_share'.tr(),
-                style: TextStyle(
-                  fontWeight: FontWeight.bold,
+      appBar: AppBar(
+        elevation: 0,
+        centerTitle: false,
+        leading: IconButton(
+          onPressed: () {
+            ref.watch(assetSelectionProvider.notifier).removeAll();
+            AutoRouter.of(context).pop();
+          },
+          icon: const Icon(Icons.close_rounded),
+        ),
+        title: const Text(
+          'share_create_album',
+          style: TextStyle(color: Colors.black),
+        ).tr(),
+        actions: [
+          TextButton(
+            onPressed: albumTitleController.text.isNotEmpty
+                ? _showSelectUserPage
+                : null,
+            child: Text(
+              'create_shared_album_page_share'.tr(),
+              style: const TextStyle(
+                fontWeight: FontWeight.bold,
+              ),
+            ),
+          ),
+        ],
+      ),
+      body: GestureDetector(
+        onTap: _onBackgroundTapped,
+        child: CustomScrollView(
+          slivers: [
+            SliverAppBar(
+              elevation: 5,
+              automaticallyImplyLeading: false,
+              // leading: Container(),
+              pinned: true,
+              floating: false,
+              bottom: PreferredSize(
+                preferredSize: const Size.fromHeight(66.0),
+                child: Column(
+                  children: [
+                    _buildTitleInputField(),
+                    if (selectedAssets.isNotEmpty) _buildControlButton(),
+                  ],
                 ),
               ),
             ),
+            _buildTitle(),
+            _buildSelectPhotosButton(),
+            _buildSelectedImageGrid(),
           ],
         ),
-        body: GestureDetector(
-          onTap: _onBackgroundTapped,
-          child: CustomScrollView(
-            slivers: [
-              SliverAppBar(
-                elevation: 5,
-                automaticallyImplyLeading: false,
-                // leading: Container(),
-                pinned: true,
-                floating: false,
-                bottom: PreferredSize(
-                  preferredSize: const Size.fromHeight(66.0),
-                  child: Column(
-                    children: [
-                      _buildTitleInputField(),
-                      if (selectedAssets.isNotEmpty) _buildControlButton(),
-                    ],
-                  ),
-                ),
-              ),
-              _buildTitle(),
-              _buildSelectPhotosButton(),
-              _buildSelectedImageGrid(),
-            ],
-          ),
-        ));
+      ),
+    );
   }
 }
diff --git a/mobile/lib/modules/sharing/views/select_additional_user_for_sharing_page.dart b/mobile/lib/modules/sharing/views/select_additional_user_for_sharing_page.dart
index 59ebc7e009..21fd7acb72 100644
--- a/mobile/lib/modules/sharing/views/select_additional_user_for_sharing_page.dart
+++ b/mobile/lib/modules/sharing/views/select_additional_user_for_sharing_page.dart
@@ -3,29 +3,28 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
 import 'package:immich_mobile/modules/sharing/providers/suggested_shared_users.provider.dart';
-import 'package:immich_mobile/shared/models/user.model.dart';
 import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+import 'package:openapi/api.dart';
 
 class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
-  final SharedAlbum albumInfo;
+  final AlbumResponseDto albumInfo;
 
   const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo})
       : super(key: key);
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    AsyncValue<List<User>> suggestedShareUsers =
+    AsyncValue<List<UserResponseDto>> suggestedShareUsers =
         ref.watch(suggestedSharedUsersProvider);
-    final sharedUsersList = useState<Set<User>>({});
+    final sharedUsersList = useState<Set<UserResponseDto>>({});
 
     _addNewUsersHandler() {
       AutoRouter.of(context)
           .pop(sharedUsersList.value.map((e) => e.id).toList());
     }
 
-    _buildTileIcon(User user) {
+    _buildTileIcon(UserResponseDto user) {
       if (sharedUsersList.value.contains(user)) {
         return CircleAvatar(
           backgroundColor: Theme.of(context).primaryColor,
@@ -43,7 +42,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
       }
     }
 
-    _buildUserList(List<User> users) {
+    _buildUserList(List<UserResponseDto> users) {
       List<Widget> usersChip = [];
 
       for (var user in sharedUsersList.value) {
@@ -55,9 +54,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
               label: Text(
                 user.email,
                 style: const TextStyle(
-                    fontSize: 12,
-                    color: Colors.black87,
-                    fontWeight: FontWeight.bold),
+                  fontSize: 12,
+                  color: Colors.black87,
+                  fontWeight: FontWeight.bold,
+                ),
               ),
             ),
           ),
@@ -70,13 +70,14 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
             children: [...usersChip],
           ),
           Padding(
-            padding: EdgeInsets.all(16.0),
+            padding: const EdgeInsets.all(16.0),
             child: Text(
               'select_additional_user_for_sharing_page_suggestions'.tr(),
-              style: TextStyle(
-                  fontSize: 14,
-                  color: Colors.grey,
-                  fontWeight: FontWeight.bold),
+              style: const TextStyle(
+                fontSize: 14,
+                color: Colors.grey,
+                fontWeight: FontWeight.bold,
+              ),
             ),
           ),
           ListView.builder(
@@ -87,13 +88,16 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
                 title: Text(
                   users[index].email,
                   style: const TextStyle(
-                      fontSize: 14, fontWeight: FontWeight.bold),
+                    fontSize: 14,
+                    fontWeight: FontWeight.bold,
+                  ),
                 ),
                 onTap: () {
                   if (sharedUsersList.value.contains(users[index])) {
                     sharedUsersList.value = sharedUsersList.value
-                        .where((selectedUser) =>
-                            selectedUser.id != users[index].id)
+                        .where(
+                          (selectedUser) => selectedUser.id != users[index].id,
+                        )
                         .toSet();
                   } else {
                     sharedUsersList.value = {
@@ -139,7 +143,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
         data: (users) {
           for (var sharedUsers in albumInfo.sharedUsers) {
             users.removeWhere(
-                (u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId);
+              (u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId,
+            );
           }
 
           return _buildUserList(users);
diff --git a/mobile/lib/modules/sharing/views/select_user_for_sharing_page.dart b/mobile/lib/modules/sharing/views/select_user_for_sharing_page.dart
index 0e0efc4878..74c5c59df6 100644
--- a/mobile/lib/modules/sharing/views/select_user_for_sharing_page.dart
+++ b/mobile/lib/modules/sharing/views/select_user_for_sharing_page.dart
@@ -9,15 +9,16 @@ import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.da
 import 'package:immich_mobile/modules/sharing/providers/suggested_shared_users.provider.dart';
 import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
 import 'package:immich_mobile/routing/router.dart';
-import 'package:immich_mobile/shared/models/user.model.dart';
 import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
+import 'package:openapi/api.dart';
 
 class SelectUserForSharingPage extends HookConsumerWidget {
   const SelectUserForSharingPage({Key? key}) : super(key: key);
+
   @override
   Widget build(BuildContext context, WidgetRef ref) {
-    final sharedUsersList = useState<Set<User>>({});
-    AsyncValue<List<User>> suggestedShareUsers =
+    final sharedUsersList = useState<Set<UserResponseDto>>({});
+    AsyncValue<List<UserResponseDto>> suggestedShareUsers =
         ref.watch(suggestedSharedUsersProvider);
 
     _createSharedAlbum() async {
@@ -37,10 +38,14 @@ class SelectUserForSharingPage extends HookConsumerWidget {
             .navigate(const TabControllerRoute(children: [SharingRoute()]));
       }
 
-      ScaffoldMessenger(child: SnackBar(content: Text('select_user_for_sharing_page_err_album').tr()));
+      ScaffoldMessenger(
+        child: SnackBar(
+          content: const Text('select_user_for_sharing_page_err_album').tr(),
+        ),
+      );
     }
 
-    _buildTileIcon(User user) {
+    _buildTileIcon(UserResponseDto user) {
       if (sharedUsersList.value.contains(user)) {
         return CircleAvatar(
           backgroundColor: Theme.of(context).primaryColor,
@@ -58,7 +63,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
       }
     }
 
-    _buildUserList(List<User> users) {
+    _buildUserList(List<UserResponseDto> users) {
       List<Widget> usersChip = [];
 
       for (var user in sharedUsersList.value) {
@@ -70,9 +75,10 @@ class SelectUserForSharingPage extends HookConsumerWidget {
               label: Text(
                 user.email,
                 style: const TextStyle(
-                    fontSize: 12,
-                    color: Colors.black87,
-                    fontWeight: FontWeight.bold),
+                  fontSize: 12,
+                  color: Colors.black87,
+                  fontWeight: FontWeight.bold,
+                ),
               ),
             ),
           ),
@@ -85,13 +91,14 @@ class SelectUserForSharingPage extends HookConsumerWidget {
             children: [...usersChip],
           ),
           Padding(
-            padding: EdgeInsets.all(16.0),
-            child: Text(
-              'share_suggestions',
+            padding: const EdgeInsets.all(16.0),
+            child: const Text(
+              'select_user_for_sharing_page_share_suggestions',
               style: TextStyle(
-                  fontSize: 14,
-                  color: Colors.grey,
-                  fontWeight: FontWeight.bold),
+                fontSize: 14,
+                color: Colors.grey,
+                fontWeight: FontWeight.bold,
+              ),
             ).tr(),
           ),
           ListView.builder(
@@ -102,13 +109,16 @@ class SelectUserForSharingPage extends HookConsumerWidget {
                 title: Text(
                   users[index].email,
                   style: const TextStyle(
-                      fontSize: 14, fontWeight: FontWeight.bold),
+                    fontSize: 14,
+                    fontWeight: FontWeight.bold,
+                  ),
                 ),
                 onTap: () {
                   if (sharedUsersList.value.contains(users[index])) {
                     sharedUsersList.value = sharedUsersList.value
-                        .where((selectedUser) =>
-                            selectedUser.id != users[index].id)
+                        .where(
+                          (selectedUser) => selectedUser.id != users[index].id,
+                        )
                         .toSet();
                   } else {
                     sharedUsersList.value = {
@@ -141,12 +151,13 @@ class SelectUserForSharingPage extends HookConsumerWidget {
         ),
         actions: [
           TextButton(
-              onPressed:
-                  sharedUsersList.value.isEmpty ? null : _createSharedAlbum,
-              child: const Text(
-                "share_create_album",
-                style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
-              ).tr())
+            onPressed:
+                sharedUsersList.value.isEmpty ? null : _createSharedAlbum,
+            child: const Text(
+              "share_create_album",
+              style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
+            ).tr(),
+          )
         ],
       ),
       body: suggestedShareUsers.when(
diff --git a/mobile/lib/modules/sharing/views/sharing_page.dart b/mobile/lib/modules/sharing/views/sharing_page.dart
index 616b55b4b9..728d52673c 100644
--- a/mobile/lib/modules/sharing/views/sharing_page.dart
+++ b/mobile/lib/modules/sharing/views/sharing_page.dart
@@ -5,10 +5,10 @@ import 'package:flutter_hooks/flutter_hooks.dart';
 import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
 import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
 import 'package:immich_mobile/modules/sharing/ui/sharing_sliver_appbar.dart';
 import 'package:immich_mobile/routing/router.dart';
+import 'package:openapi/api.dart';
 import 'package:transparent_image/transparent_image.dart';
 
 class SharingPage extends HookConsumerWidget {
@@ -18,13 +18,16 @@ class SharingPage extends HookConsumerWidget {
   Widget build(BuildContext context, WidgetRef ref) {
     var box = Hive.box(userInfoBox);
     var thumbnailRequestUrl = '${box.get(serverEndpointKey)}/asset/thumbnail';
-    final List<SharedAlbum> sharedAlbums = ref.watch(sharedAlbumProvider);
+    final List<AlbumResponseDto> sharedAlbums = ref.watch(sharedAlbumProvider);
 
-    useEffect(() {
-      ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
+    useEffect(
+      () {
+        ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
 
-      return null;
-    }, []);
+        return null;
+      },
+      [],
+    );
 
     _buildAlbumList() {
       return SliverList(
@@ -60,9 +63,10 @@ class SharingPage extends HookConsumerWidget {
                 maxLines: 1,
                 overflow: TextOverflow.ellipsis,
                 style: TextStyle(
-                    fontSize: 16,
-                    fontWeight: FontWeight.bold,
-                    color: Colors.grey.shade800),
+                  fontSize: 16,
+                  fontWeight: FontWeight.bold,
+                  color: Colors.grey.shade800,
+                ),
               ),
               onTap: () {
                 AutoRouter.of(context)
@@ -133,9 +137,9 @@ class SharingPage extends HookConsumerWidget {
         slivers: [
           const SharingSliverAppBar(),
           SliverPadding(
-            padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
+            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
             sliver: SliverToBoxAdapter(
-              child: Text(
+              child: const Text(
                 "sharing_page_album",
                 style: TextStyle(
                   fontWeight: FontWeight.bold,
diff --git a/mobile/lib/routing/auth_guard.dart b/mobile/lib/routing/auth_guard.dart
index 3677a4f631..692cd4e38c 100644
--- a/mobile/lib/routing/auth_guard.dart
+++ b/mobile/lib/routing/auth_guard.dart
@@ -1,21 +1,25 @@
-import 'dart:convert';
-
 import 'package:auto_route/auto_route.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
+import 'package:flutter/foundation.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
 
 class AuthGuard extends AutoRouteGuard {
-  final NetworkService _networkService = NetworkService();
-
+  final ApiService _apiService;
+  AuthGuard(this._apiService);
   @override
   void onNavigation(NavigationResolver resolver, StackRouter router) async {
     try {
-      var res = await _networkService.postRequest(url: 'auth/validateToken');
-      var jsonReponse = jsonDecode(res.toString());
-      if (jsonReponse['authStatus']) {
+      var res = await _apiService.authenticationApi.validateAccessToken();
+
+      if (res != null && res.authStatus) {
         resolver.next(true);
+      } else {
+        router.replaceAll([const LoginRoute()]);
       }
     } catch (e) {
-      router.removeUntil((route) => route.name == "LoginRoute");
+      debugPrint("Error [onNavigation] ${e.toString()}");
+      router.replaceAll([const LoginRoute()]);
+      return;
     }
   }
 }
diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart
index 6bce788af9..a964b46951 100644
--- a/mobile/lib/routing/router.dart
+++ b/mobile/lib/routing/router.dart
@@ -1,5 +1,6 @@
 import 'package:auto_route/auto_route.dart';
 import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
 import 'package:immich_mobile/modules/backup/views/backup_album_selection_page.dart';
 import 'package:immich_mobile/modules/backup/views/failed_backup_status_page.dart';
@@ -9,7 +10,6 @@ import 'package:immich_mobile/modules/home/views/home_page.dart';
 import 'package:immich_mobile/modules/search/views/search_page.dart';
 import 'package:immich_mobile/modules/search/views/search_result_page.dart';
 import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
-import 'package:immich_mobile/modules/sharing/models/shared_album.model.dart';
 import 'package:immich_mobile/modules/sharing/views/album_viewer_page.dart';
 import 'package:immich_mobile/modules/sharing/views/asset_selection_page.dart';
 import 'package:immich_mobile/modules/sharing/views/create_shared_album_page.dart';
@@ -17,12 +17,13 @@ import 'package:immich_mobile/modules/sharing/views/select_additional_user_for_s
 import 'package:immich_mobile/modules/sharing/views/select_user_for_sharing_page.dart';
 import 'package:immich_mobile/modules/sharing/views/sharing_page.dart';
 import 'package:immich_mobile/routing/auth_guard.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:immich_mobile/modules/backup/views/backup_controller_page.dart';
 import 'package:immich_mobile/modules/asset_viewer/views/image_viewer_page.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
 import 'package:immich_mobile/shared/views/splash_screen.dart';
 import 'package:immich_mobile/shared/views/tab_controller_page.dart';
 import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
+import 'package:openapi/api.dart';
 import 'package:photo_manager/photo_manager.dart';
 
 part 'router.gr.dart';
@@ -74,5 +75,9 @@ part 'router.gr.dart';
   ],
 )
 class AppRouter extends _$AppRouter {
-  AppRouter() : super(authGuard: AuthGuard());
+  final ApiService _apiService;
+  AppRouter(this._apiService) : super(authGuard: AuthGuard(_apiService));
 }
+
+final appRouterProvider =
+    Provider((ref) => AppRouter(ref.watch(apiServiceProvider)));
diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart
index 16042b45a3..79ce762d01 100644
--- a/mobile/lib/routing/router.gr.dart
+++ b/mobile/lib/routing/router.gr.dart
@@ -234,7 +234,7 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> {
       required String imageUrl,
       required String heroTag,
       required String thumbnailUrl,
-      required ImmichAsset asset})
+      required AssetResponseDto asset})
       : super(ImageViewerRoute.name,
             path: '/image-viewer-page',
             args: ImageViewerRouteArgs(
@@ -263,7 +263,7 @@ class ImageViewerRouteArgs {
 
   final String thumbnailUrl;
 
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
 
   @override
   String toString() {
@@ -275,7 +275,7 @@ class ImageViewerRouteArgs {
 /// [VideoViewerPage]
 class VideoViewerRoute extends PageRouteInfo<VideoViewerRouteArgs> {
   VideoViewerRoute(
-      {Key? key, required String videoUrl, required ImmichAsset asset})
+      {Key? key, required String videoUrl, required AssetResponseDto asset})
       : super(VideoViewerRoute.name,
             path: '/video-viewer-page',
             args: VideoViewerRouteArgs(
@@ -292,7 +292,7 @@ class VideoViewerRouteArgs {
 
   final String videoUrl;
 
-  final ImmichAsset asset;
+  final AssetResponseDto asset;
 
   @override
   String toString() {
@@ -390,7 +390,7 @@ class AlbumViewerRouteArgs {
 class SelectAdditionalUserForSharingRoute
     extends PageRouteInfo<SelectAdditionalUserForSharingRouteArgs> {
   SelectAdditionalUserForSharingRoute(
-      {Key? key, required SharedAlbum albumInfo})
+      {Key? key, required AlbumResponseDto albumInfo})
       : super(SelectAdditionalUserForSharingRoute.name,
             path: '/select-additional-user-for-sharing-page',
             args: SelectAdditionalUserForSharingRouteArgs(
@@ -405,7 +405,7 @@ class SelectAdditionalUserForSharingRouteArgs {
 
   final Key? key;
 
-  final SharedAlbum albumInfo;
+  final AlbumResponseDto albumInfo;
 
   @override
   String toString() {
diff --git a/mobile/lib/routing/tab_navigation_observer.dart b/mobile/lib/routing/tab_navigation_observer.dart
index 5eecc7ec37..bac868cf30 100644
--- a/mobile/lib/routing/tab_navigation_observer.dart
+++ b/mobile/lib/routing/tab_navigation_observer.dart
@@ -23,7 +23,9 @@ class TabNavigationObserver extends AutoRouterObserver {
 
   @override
   Future<void> didChangeTabRoute(
-      TabPageRoute route, TabPageRoute previousRoute) async {
+    TabPageRoute route,
+    TabPageRoute previousRoute,
+  ) async {
     // Perform tasks on re-visit to SearchRoute
     if (route.name == 'SearchRoute') {
       // Refresh Location State
diff --git a/mobile/lib/shared/models/device_info.model.dart b/mobile/lib/shared/models/device_info.model.dart
deleted file mode 100644
index 0792e9109e..0000000000
--- a/mobile/lib/shared/models/device_info.model.dart
+++ /dev/null
@@ -1,100 +0,0 @@
-import 'dart:convert';
-
-class DeviceInfoRemote {
-  final int id;
-  final String userId;
-  final String deviceId;
-  final String deviceType;
-  final String notificationToken;
-  final String createdAt;
-  final bool isAutoBackup;
-
-  DeviceInfoRemote({
-    required this.id,
-    required this.userId,
-    required this.deviceId,
-    required this.deviceType,
-    required this.notificationToken,
-    required this.createdAt,
-    required this.isAutoBackup,
-  });
-
-  DeviceInfoRemote copyWith({
-    int? id,
-    String? userId,
-    String? deviceId,
-    String? deviceType,
-    String? notificationToken,
-    String? createdAt,
-    bool? isAutoBackup,
-  }) {
-    return DeviceInfoRemote(
-      id: id ?? this.id,
-      userId: userId ?? this.userId,
-      deviceId: deviceId ?? this.deviceId,
-      deviceType: deviceType ?? this.deviceType,
-      notificationToken: notificationToken ?? this.notificationToken,
-      createdAt: createdAt ?? this.createdAt,
-      isAutoBackup: isAutoBackup ?? this.isAutoBackup,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'id': id,
-      'userId': userId,
-      'deviceId': deviceId,
-      'deviceType': deviceType,
-      'notificationToken': notificationToken,
-      'createdAt': createdAt,
-      'isAutoBackup': isAutoBackup,
-    };
-  }
-
-  factory DeviceInfoRemote.fromMap(Map<String, dynamic> map) {
-    return DeviceInfoRemote(
-      id: map['id']?.toInt() ?? 0,
-      userId: map['userId'] ?? '',
-      deviceId: map['deviceId'] ?? '',
-      deviceType: map['deviceType'] ?? '',
-      notificationToken: map['notificationToken'] ?? '',
-      createdAt: map['createdAt'] ?? '',
-      isAutoBackup: map['isAutoBackup'] ?? false,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory DeviceInfoRemote.fromJson(String source) =>
-      DeviceInfoRemote.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'DeviceInfo(id: $id, userId: $userId, deviceId: $deviceId, deviceType: $deviceType, notificationToken: $notificationToken, createdAt: $createdAt, isAutoBackup: $isAutoBackup)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is DeviceInfoRemote &&
-        other.id == id &&
-        other.userId == userId &&
-        other.deviceId == deviceId &&
-        other.deviceType == deviceType &&
-        other.notificationToken == notificationToken &&
-        other.createdAt == createdAt &&
-        other.isAutoBackup == isAutoBackup;
-  }
-
-  @override
-  int get hashCode {
-    return id.hashCode ^
-        userId.hashCode ^
-        deviceId.hashCode ^
-        deviceType.hashCode ^
-        notificationToken.hashCode ^
-        createdAt.hashCode ^
-        isAutoBackup.hashCode;
-  }
-}
diff --git a/mobile/lib/shared/models/exif.model.dart b/mobile/lib/shared/models/exif.model.dart
deleted file mode 100644
index 7d8528b363..0000000000
--- a/mobile/lib/shared/models/exif.model.dart
+++ /dev/null
@@ -1,212 +0,0 @@
-import 'dart:convert';
-
-class ImmichExif {
-  final int? id;
-  final String? assetId;
-  final String? make;
-  final String? model;
-  final String? imageName;
-  final int? exifImageWidth;
-  final int? exifImageHeight;
-  final int? fileSizeInByte;
-  final String? orientation;
-  final String? dateTimeOriginal;
-  final String? modifyDate;
-  final String? lensModel;
-  final double? fNumber;
-  final double? focalLength;
-  final int? iso;
-  final double? exposureTime;
-  final double? latitude;
-  final double? longitude;
-  final String? city;
-  final String? state;
-  final String? country;
-
-  ImmichExif({
-    this.id,
-    this.assetId,
-    this.make,
-    this.model,
-    this.imageName,
-    this.exifImageWidth,
-    this.exifImageHeight,
-    this.fileSizeInByte,
-    this.orientation,
-    this.dateTimeOriginal,
-    this.modifyDate,
-    this.lensModel,
-    this.fNumber,
-    this.focalLength,
-    this.iso,
-    this.exposureTime,
-    this.latitude,
-    this.longitude,
-    this.city,
-    this.state,
-    this.country,
-  });
-
-  ImmichExif copyWith({
-    int? id,
-    String? assetId,
-    String? make,
-    String? model,
-    String? imageName,
-    int? exifImageWidth,
-    int? exifImageHeight,
-    int? fileSizeInByte,
-    String? orientation,
-    String? dateTimeOriginal,
-    String? modifyDate,
-    String? lensModel,
-    double? fNumber,
-    double? focalLength,
-    int? iso,
-    double? exposureTime,
-    double? latitude,
-    double? longitude,
-    String? city,
-    String? state,
-    String? country,
-  }) {
-    return ImmichExif(
-      id: id ?? this.id,
-      assetId: assetId ?? this.assetId,
-      make: make ?? this.make,
-      model: model ?? this.model,
-      imageName: imageName ?? this.imageName,
-      exifImageWidth: exifImageWidth ?? this.exifImageWidth,
-      exifImageHeight: exifImageHeight ?? this.exifImageHeight,
-      fileSizeInByte: fileSizeInByte ?? this.fileSizeInByte,
-      orientation: orientation ?? this.orientation,
-      dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal,
-      modifyDate: modifyDate ?? this.modifyDate,
-      lensModel: lensModel ?? this.lensModel,
-      fNumber: fNumber ?? this.fNumber,
-      focalLength: focalLength ?? this.focalLength,
-      iso: iso ?? this.iso,
-      exposureTime: exposureTime ?? this.exposureTime,
-      latitude: latitude ?? this.latitude,
-      longitude: longitude ?? this.longitude,
-      city: city ?? this.city,
-      state: state ?? this.state,
-      country: country ?? this.country,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'id': id,
-      'assetId': assetId,
-      'make': make,
-      'model': model,
-      'imageName': imageName,
-      'exifImageWidth': exifImageWidth,
-      'exifImageHeight': exifImageHeight,
-      'fileSizeInByte': fileSizeInByte,
-      'orientation': orientation,
-      'dateTimeOriginal': dateTimeOriginal,
-      'modifyDate': modifyDate,
-      'lensModel': lensModel,
-      'fNumber': fNumber,
-      'focalLength': focalLength,
-      'iso': iso,
-      'exposureTime': exposureTime,
-      'latitude': latitude,
-      'longitude': longitude,
-      'city': city,
-      'state': state,
-      'country': country,
-    };
-  }
-
-  factory ImmichExif.fromMap(Map<String, dynamic> map) {
-    return ImmichExif(
-      id: map['id']?.toInt(),
-      assetId: map['assetId'],
-      make: map['make'],
-      model: map['model'],
-      imageName: map['imageName'],
-      exifImageWidth: map['exifImageWidth']?.toInt(),
-      exifImageHeight: map['exifImageHeight']?.toInt(),
-      fileSizeInByte: map['fileSizeInByte']?.toInt(),
-      orientation: map['orientation'],
-      dateTimeOriginal: map['dateTimeOriginal'],
-      modifyDate: map['modifyDate'],
-      lensModel: map['lensModel'],
-      fNumber: map['fNumber']?.toDouble(),
-      focalLength: map['focalLength']?.toDouble(),
-      iso: map['iso']?.toInt(),
-      exposureTime: map['exposureTime']?.toDouble(),
-      latitude: map['latitude']?.toDouble(),
-      longitude: map['longitude']?.toDouble(),
-      city: map['city'],
-      state: map['state'],
-      country: map['country'],
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory ImmichExif.fromJson(String source) =>
-      ImmichExif.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'ImmichExif(id: $id, assetId: $assetId, make: $make, model: $model, imageName: $imageName, exifImageWidth: $exifImageWidth, exifImageHeight: $exifImageHeight, fileSizeInByte: $fileSizeInByte, orientation: $orientation, dateTimeOriginal: $dateTimeOriginal, modifyDate: $modifyDate, lensModel: $lensModel, fNumber: $fNumber, focalLength: $focalLength, iso: $iso, exposureTime: $exposureTime, latitude: $latitude, longitude: $longitude, city: $city, state: $state, country: $country)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is ImmichExif &&
-        other.id == id &&
-        other.assetId == assetId &&
-        other.make == make &&
-        other.model == model &&
-        other.imageName == imageName &&
-        other.exifImageWidth == exifImageWidth &&
-        other.exifImageHeight == exifImageHeight &&
-        other.fileSizeInByte == fileSizeInByte &&
-        other.orientation == orientation &&
-        other.dateTimeOriginal == dateTimeOriginal &&
-        other.modifyDate == modifyDate &&
-        other.lensModel == lensModel &&
-        other.fNumber == fNumber &&
-        other.focalLength == focalLength &&
-        other.iso == iso &&
-        other.exposureTime == exposureTime &&
-        other.latitude == latitude &&
-        other.longitude == longitude &&
-        other.city == city &&
-        other.state == state &&
-        other.country == country;
-  }
-
-  @override
-  int get hashCode {
-    return id.hashCode ^
-        assetId.hashCode ^
-        make.hashCode ^
-        model.hashCode ^
-        imageName.hashCode ^
-        exifImageWidth.hashCode ^
-        exifImageHeight.hashCode ^
-        fileSizeInByte.hashCode ^
-        orientation.hashCode ^
-        dateTimeOriginal.hashCode ^
-        modifyDate.hashCode ^
-        lensModel.hashCode ^
-        fNumber.hashCode ^
-        focalLength.hashCode ^
-        iso.hashCode ^
-        exposureTime.hashCode ^
-        latitude.hashCode ^
-        longitude.hashCode ^
-        city.hashCode ^
-        state.hashCode ^
-        country.hashCode;
-  }
-}
diff --git a/mobile/lib/shared/models/immich_asset.model.dart b/mobile/lib/shared/models/immich_asset.model.dart
deleted file mode 100644
index 558c19f2b5..0000000000
--- a/mobile/lib/shared/models/immich_asset.model.dart
+++ /dev/null
@@ -1,110 +0,0 @@
-import 'dart:convert';
-
-import 'package:equatable/equatable.dart';
-
-class ImmichAsset extends Equatable {
-  final String id;
-  final String deviceAssetId;
-  final String userId;
-  final String deviceId;
-  final String type;
-  final String createdAt;
-  final String modifiedAt;
-  final bool isFavorite;
-  final String? duration;
-  final String originalPath;
-  final String resizePath;
-
-  const ImmichAsset({
-    required this.id,
-    required this.deviceAssetId,
-    required this.userId,
-    required this.deviceId,
-    required this.type,
-    required this.createdAt,
-    required this.modifiedAt,
-    required this.isFavorite,
-    this.duration,
-    required this.originalPath,
-    required this.resizePath,
-  });
-
-  ImmichAsset copyWith({
-    String? id,
-    String? deviceAssetId,
-    String? userId,
-    String? deviceId,
-    String? type,
-    String? createdAt,
-    String? modifiedAt,
-    bool? isFavorite,
-    String? duration,
-    String? originalPath,
-    String? resizePath,
-  }) {
-    return ImmichAsset(
-      id: id ?? this.id,
-      deviceAssetId: deviceAssetId ?? this.deviceAssetId,
-      userId: userId ?? this.userId,
-      deviceId: deviceId ?? this.deviceId,
-      type: type ?? this.type,
-      createdAt: createdAt ?? this.createdAt,
-      modifiedAt: modifiedAt ?? this.modifiedAt,
-      isFavorite: isFavorite ?? this.isFavorite,
-      duration: duration ?? this.duration,
-      originalPath: originalPath ?? this.originalPath,
-      resizePath: resizePath ?? this.resizePath,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll({'id': id});
-    result.addAll({'deviceAssetId': deviceAssetId});
-    result.addAll({'userId': userId});
-    result.addAll({'deviceId': deviceId});
-    result.addAll({'type': type});
-    result.addAll({'createdAt': createdAt});
-    result.addAll({'modifiedAt': modifiedAt});
-    result.addAll({'isFavorite': isFavorite});
-    if (duration != null) {
-      result.addAll({'duration': duration});
-    }
-    result.addAll({'originalPath': originalPath});
-    result.addAll({'resizePath': resizePath});
-
-    return result;
-  }
-
-  factory ImmichAsset.fromMap(Map<String, dynamic> map) {
-    return ImmichAsset(
-      id: map['id'] ?? '',
-      deviceAssetId: map['deviceAssetId'] ?? '',
-      userId: map['userId'] ?? '',
-      deviceId: map['deviceId'] ?? '',
-      type: map['type'] ?? '',
-      createdAt: map['createdAt'] ?? '',
-      modifiedAt: map['modifiedAt'] ?? '',
-      isFavorite: map['isFavorite'] ?? false,
-      duration: map['duration'],
-      originalPath: map['originalPath'] ?? '',
-      resizePath: map['resizePath'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory ImmichAsset.fromJson(String source) =>
-      ImmichAsset.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'ImmichAsset(id: $id, deviceAssetId: $deviceAssetId, userId: $userId, deviceId: $deviceId, type: $type, createdAt: $createdAt, modifiedAt: $modifiedAt, isFavorite: $isFavorite, duration: $duration, originalPath: $originalPath, resizePath: $resizePath)';
-  }
-
-  @override
-  List<Object> get props {
-    return [id];
-  }
-}
diff --git a/mobile/lib/shared/models/immich_asset_with_exif.model.dart b/mobile/lib/shared/models/immich_asset_with_exif.model.dart
deleted file mode 100644
index 88ff974373..0000000000
--- a/mobile/lib/shared/models/immich_asset_with_exif.model.dart
+++ /dev/null
@@ -1,135 +0,0 @@
-import 'dart:convert';
-
-import 'package:immich_mobile/shared/models/exif.model.dart';
-
-class ImmichAssetWithExif {
-  final String id;
-  final String deviceAssetId;
-  final String userId;
-  final String deviceId;
-  final String type;
-  final String createdAt;
-  final String modifiedAt;
-  final String originalPath;
-  final bool isFavorite;
-  final String? duration;
-  final ImmichExif? exifInfo;
-
-  ImmichAssetWithExif({
-    required this.id,
-    required this.deviceAssetId,
-    required this.userId,
-    required this.deviceId,
-    required this.type,
-    required this.createdAt,
-    required this.modifiedAt,
-    required this.originalPath,
-    required this.isFavorite,
-    this.duration,
-    this.exifInfo,
-  });
-
-  ImmichAssetWithExif copyWith({
-    String? id,
-    String? deviceAssetId,
-    String? userId,
-    String? deviceId,
-    String? type,
-    String? createdAt,
-    String? modifiedAt,
-    String? originalPath,
-    bool? isFavorite,
-    String? duration,
-    ImmichExif? exifInfo,
-  }) {
-    return ImmichAssetWithExif(
-      id: id ?? this.id,
-      deviceAssetId: deviceAssetId ?? this.deviceAssetId,
-      userId: userId ?? this.userId,
-      deviceId: deviceId ?? this.deviceId,
-      type: type ?? this.type,
-      createdAt: createdAt ?? this.createdAt,
-      modifiedAt: modifiedAt ?? this.modifiedAt,
-      originalPath: originalPath ?? this.originalPath,
-      isFavorite: isFavorite ?? this.isFavorite,
-      duration: duration ?? this.duration,
-      exifInfo: exifInfo ?? this.exifInfo,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'id': id,
-      'deviceAssetId': deviceAssetId,
-      'userId': userId,
-      'deviceId': deviceId,
-      'type': type,
-      'createdAt': createdAt,
-      'modifiedAt': modifiedAt,
-      'originalPath': originalPath,
-      'isFavorite': isFavorite,
-      'duration': duration,
-      'exifInfo': exifInfo?.toMap(),
-    };
-  }
-
-  factory ImmichAssetWithExif.fromMap(Map<String, dynamic> map) {
-    return ImmichAssetWithExif(
-      id: map['id'] ?? '',
-      deviceAssetId: map['deviceAssetId'] ?? '',
-      userId: map['userId'] ?? '',
-      deviceId: map['deviceId'] ?? '',
-      type: map['type'] ?? '',
-      createdAt: map['createdAt'] ?? '',
-      modifiedAt: map['modifiedAt'] ?? '',
-      originalPath: map['originalPath'] ?? '',
-      isFavorite: map['isFavorite'] ?? false,
-      duration: map['duration'],
-      exifInfo:
-          map['exifInfo'] != null ? ImmichExif.fromMap(map['exifInfo']) : null,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory ImmichAssetWithExif.fromJson(String source) =>
-      ImmichAssetWithExif.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'ImmichAssetWithExif(id: $id, deviceAssetId: $deviceAssetId, userId: $userId, deviceId: $deviceId, type: $type, createdAt: $createdAt, modifiedAt: $modifiedAt, originalPath: $originalPath, isFavorite: $isFavorite, duration: $duration, exifInfo: $exifInfo)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is ImmichAssetWithExif &&
-        other.id == id &&
-        other.deviceAssetId == deviceAssetId &&
-        other.userId == userId &&
-        other.deviceId == deviceId &&
-        other.type == type &&
-        other.createdAt == createdAt &&
-        other.modifiedAt == modifiedAt &&
-        other.originalPath == originalPath &&
-        other.isFavorite == isFavorite &&
-        other.duration == duration &&
-        other.exifInfo == exifInfo;
-  }
-
-  @override
-  int get hashCode {
-    return id.hashCode ^
-        deviceAssetId.hashCode ^
-        userId.hashCode ^
-        deviceId.hashCode ^
-        type.hashCode ^
-        createdAt.hashCode ^
-        modifiedAt.hashCode ^
-        originalPath.hashCode ^
-        isFavorite.hashCode ^
-        duration.hashCode ^
-        exifInfo.hashCode;
-  }
-}
diff --git a/mobile/lib/shared/models/mapbox_info.model.dart b/mobile/lib/shared/models/mapbox_info.model.dart
deleted file mode 100644
index f3407bab32..0000000000
--- a/mobile/lib/shared/models/mapbox_info.model.dart
+++ /dev/null
@@ -1,55 +0,0 @@
-import 'dart:convert';
-
-class MapboxInfo {
-  final bool isEnable;
-  final String mapboxSecret;
-  MapboxInfo({
-    required this.isEnable,
-    required this.mapboxSecret,
-  });
-
-  MapboxInfo copyWith({
-    bool? isEnable,
-    String? mapboxSecret,
-  }) {
-    return MapboxInfo(
-      isEnable: isEnable ?? this.isEnable,
-      mapboxSecret: mapboxSecret ?? this.mapboxSecret,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'isEnable': isEnable,
-      'mapboxSecret': mapboxSecret,
-    };
-  }
-
-  factory MapboxInfo.fromMap(Map<String, dynamic> map) {
-    return MapboxInfo(
-      isEnable: map['isEnable'] ?? false,
-      mapboxSecret: map['mapboxSecret'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory MapboxInfo.fromJson(String source) =>
-      MapboxInfo.fromMap(json.decode(source));
-
-  @override
-  String toString() =>
-      'MapboxInfo(isEnable: $isEnable, mapboxSecret: $mapboxSecret)';
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is MapboxInfo &&
-        other.isEnable == isEnable &&
-        other.mapboxSecret == mapboxSecret;
-  }
-
-  @override
-  int get hashCode => isEnable.hashCode ^ mapboxSecret.hashCode;
-}
diff --git a/mobile/lib/shared/models/server_info.model.dart b/mobile/lib/shared/models/server_info.model.dart
deleted file mode 100644
index 7343fbe961..0000000000
--- a/mobile/lib/shared/models/server_info.model.dart
+++ /dev/null
@@ -1,99 +0,0 @@
-import 'dart:convert';
-
-class ServerInfo {
-  final String diskSize;
-  final String diskUse;
-  final String diskAvailable;
-  final int diskSizeRaw;
-  final int diskUseRaw;
-  final int diskAvailableRaw;
-  final double diskUsagePercentage;
-  ServerInfo({
-    required this.diskSize,
-    required this.diskUse,
-    required this.diskAvailable,
-    required this.diskSizeRaw,
-    required this.diskUseRaw,
-    required this.diskAvailableRaw,
-    required this.diskUsagePercentage,
-  });
-
-  ServerInfo copyWith({
-    String? diskSize,
-    String? diskUse,
-    String? diskAvailable,
-    int? diskSizeRaw,
-    int? diskUseRaw,
-    int? diskAvailableRaw,
-    double? diskUsagePercentage,
-  }) {
-    return ServerInfo(
-      diskSize: diskSize ?? this.diskSize,
-      diskUse: diskUse ?? this.diskUse,
-      diskAvailable: diskAvailable ?? this.diskAvailable,
-      diskSizeRaw: diskSizeRaw ?? this.diskSizeRaw,
-      diskUseRaw: diskUseRaw ?? this.diskUseRaw,
-      diskAvailableRaw: diskAvailableRaw ?? this.diskAvailableRaw,
-      diskUsagePercentage: diskUsagePercentage ?? this.diskUsagePercentage,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'diskSize': diskSize,
-      'diskUse': diskUse,
-      'diskAvailable': diskAvailable,
-      'diskSizeRaw': diskSizeRaw,
-      'diskUseRaw': diskUseRaw,
-      'diskAvailableRaw': diskAvailableRaw,
-      'diskUsagePercentage': diskUsagePercentage,
-    };
-  }
-
-  factory ServerInfo.fromMap(Map<String, dynamic> map) {
-    return ServerInfo(
-      diskSize: map['diskSize'] ?? '',
-      diskUse: map['diskUse'] ?? '',
-      diskAvailable: map['diskAvailable'] ?? '',
-      diskSizeRaw: map['diskSizeRaw']?.toInt() ?? 0,
-      diskUseRaw: map['diskUseRaw']?.toInt() ?? 0,
-      diskAvailableRaw: map['diskAvailableRaw']?.toInt() ?? 0,
-      diskUsagePercentage: map['diskUsagePercentage']?.toDouble() ?? 0.0,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory ServerInfo.fromJson(String source) =>
-      ServerInfo.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'ServerInfo(diskSize: $diskSize, diskUse: $diskUse, diskAvailable: $diskAvailable, diskSizeRaw: $diskSizeRaw, diskUseRaw: $diskUseRaw, diskAvailableRaw: $diskAvailableRaw, diskUsagePercentage: $diskUsagePercentage)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is ServerInfo &&
-        other.diskSize == diskSize &&
-        other.diskUse == diskUse &&
-        other.diskAvailable == diskAvailable &&
-        other.diskSizeRaw == diskSizeRaw &&
-        other.diskUseRaw == diskUseRaw &&
-        other.diskAvailableRaw == diskAvailableRaw &&
-        other.diskUsagePercentage == diskUsagePercentage;
-  }
-
-  @override
-  int get hashCode {
-    return diskSize.hashCode ^
-        diskUse.hashCode ^
-        diskAvailable.hashCode ^
-        diskSizeRaw.hashCode ^
-        diskUseRaw.hashCode ^
-        diskAvailableRaw.hashCode ^
-        diskUsagePercentage.hashCode;
-  }
-}
diff --git a/mobile/lib/shared/models/server_info_state.model.dart b/mobile/lib/shared/models/server_info_state.model.dart
index f82beb7949..7b1ea9c918 100644
--- a/mobile/lib/shared/models/server_info_state.model.dart
+++ b/mobile/lib/shared/models/server_info_state.model.dart
@@ -1,29 +1,22 @@
-import 'dart:convert';
-
-import 'package:immich_mobile/shared/models/mapbox_info.model.dart';
-import 'package:immich_mobile/shared/models/server_version.model.dart';
+import 'package:openapi/api.dart';
 
 class ServerInfoState {
-  final MapboxInfo mapboxInfo;
-  final ServerVersion serverVersion;
+  final ServerVersionReponseDto serverVersion;
   final bool isVersionMismatch;
   final String versionMismatchErrorMessage;
 
   ServerInfoState({
-    required this.mapboxInfo,
     required this.serverVersion,
     required this.isVersionMismatch,
     required this.versionMismatchErrorMessage,
   });
 
   ServerInfoState copyWith({
-    MapboxInfo? mapboxInfo,
-    ServerVersion? serverVersion,
+    ServerVersionReponseDto? serverVersion,
     bool? isVersionMismatch,
     String? versionMismatchErrorMessage,
   }) {
     return ServerInfoState(
-      mapboxInfo: mapboxInfo ?? this.mapboxInfo,
       serverVersion: serverVersion ?? this.serverVersion,
       isVersionMismatch: isVersionMismatch ?? this.isVersionMismatch,
       versionMismatchErrorMessage:
@@ -31,32 +24,9 @@ class ServerInfoState {
     );
   }
 
-  Map<String, dynamic> toMap() {
-    return {
-      'mapboxInfo': mapboxInfo.toMap(),
-      'serverVersion': serverVersion.toMap(),
-      'isVersionMismatch': isVersionMismatch,
-      'versionMismatchErrorMessage': versionMismatchErrorMessage,
-    };
-  }
-
-  factory ServerInfoState.fromMap(Map<String, dynamic> map) {
-    return ServerInfoState(
-      mapboxInfo: MapboxInfo.fromMap(map['mapboxInfo']),
-      serverVersion: ServerVersion.fromMap(map['serverVersion']),
-      isVersionMismatch: map['isVersionMismatch'] ?? false,
-      versionMismatchErrorMessage: map['versionMismatchErrorMessage'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory ServerInfoState.fromJson(String source) =>
-      ServerInfoState.fromMap(json.decode(source));
-
   @override
   String toString() {
-    return 'ServerInfoState(mapboxInfo: $mapboxInfo, serverVersion: $serverVersion, isVersionMismatch: $isVersionMismatch, versionMismatchErrorMessage: $versionMismatchErrorMessage)';
+    return 'ServerInfoState( serverVersion: $serverVersion, isVersionMismatch: $isVersionMismatch, versionMismatchErrorMessage: $versionMismatchErrorMessage)';
   }
 
   @override
@@ -64,7 +34,6 @@ class ServerInfoState {
     if (identical(this, other)) return true;
 
     return other is ServerInfoState &&
-        other.mapboxInfo == mapboxInfo &&
         other.serverVersion == serverVersion &&
         other.isVersionMismatch == isVersionMismatch &&
         other.versionMismatchErrorMessage == versionMismatchErrorMessage;
@@ -72,8 +41,7 @@ class ServerInfoState {
 
   @override
   int get hashCode {
-    return mapboxInfo.hashCode ^
-        serverVersion.hashCode ^
+    return serverVersion.hashCode ^
         isVersionMismatch.hashCode ^
         versionMismatchErrorMessage.hashCode;
   }
diff --git a/mobile/lib/shared/models/server_version.model.dart b/mobile/lib/shared/models/server_version.model.dart
deleted file mode 100644
index d41fb1e133..0000000000
--- a/mobile/lib/shared/models/server_version.model.dart
+++ /dev/null
@@ -1,73 +0,0 @@
-import 'dart:convert';
-
-class ServerVersion {
-  final int major;
-  final int minor;
-  final int patch;
-  final int build;
-
-  ServerVersion({
-    required this.major,
-    required this.minor,
-    required this.patch,
-    required this.build,
-  });
-
-  ServerVersion copyWith({
-    int? major,
-    int? minor,
-    int? patch,
-    int? build,
-  }) {
-    return ServerVersion(
-      major: major ?? this.major,
-      minor: minor ?? this.minor,
-      patch: patch ?? this.patch,
-      build: build ?? this.build,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    return {
-      'major': major,
-      'minor': minor,
-      'patch': patch,
-      'build': build,
-    };
-  }
-
-  factory ServerVersion.fromMap(Map<String, dynamic> map) {
-    return ServerVersion(
-      major: map['major']?.toInt() ?? 0,
-      minor: map['minor']?.toInt() ?? 0,
-      patch: map['patch']?.toInt() ?? 0,
-      build: map['build']?.toInt() ?? 0,
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory ServerVersion.fromJson(String source) =>
-      ServerVersion.fromMap(json.decode(source));
-
-  @override
-  String toString() {
-    return 'ServerVersion(major: $major, minor: $minor, patch: $patch, build: $build)';
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is ServerVersion &&
-        other.major == major &&
-        other.minor == minor &&
-        other.patch == patch &&
-        other.build == build;
-  }
-
-  @override
-  int get hashCode {
-    return major.hashCode ^ minor.hashCode ^ patch.hashCode ^ build.hashCode;
-  }
-}
diff --git a/mobile/lib/shared/models/user.model.dart b/mobile/lib/shared/models/user.model.dart
deleted file mode 100644
index 99b0ce6cc0..0000000000
--- a/mobile/lib/shared/models/user.model.dart
+++ /dev/null
@@ -1,76 +0,0 @@
-import 'dart:convert';
-
-class User {
-  final String id;
-  final String email;
-  final String createdAt;
-  final String firstName;
-  final String lastName;
-
-  User({
-    required this.id,
-    required this.email,
-    required this.createdAt,
-    required this.firstName,
-    required this.lastName,
-  });
-
-  User copyWith({
-    String? id,
-    String? email,
-    String? createdAt,
-    String? firstName,
-    String? lastName,
-  }) {
-    return User(
-      id: id ?? this.id,
-      email: email ?? this.email,
-      createdAt: createdAt ?? this.createdAt,
-      firstName: firstName ?? this.firstName,
-      lastName: lastName ?? this.lastName,
-    );
-  }
-
-  Map<String, dynamic> toMap() {
-    final result = <String, dynamic>{};
-
-    result.addAll({'id': id});
-    result.addAll({'email': email});
-    result.addAll({'createdAt': createdAt});
-
-    return result;
-  }
-
-  factory User.fromMap(Map<String, dynamic> map) {
-    return User(
-      id: map['id'] ?? '',
-      email: map['email'] ?? '',
-      createdAt: map['createdAt'] ?? '',
-      firstName: map['firstName'] ?? '',
-      lastName: map['lastName'] ?? '',
-    );
-  }
-
-  String toJson() => json.encode(toMap());
-
-  factory User.fromJson(String source) => User.fromMap(json.decode(source));
-
-  @override
-  String toString() =>
-      'UserInfo(id: $id, email: $email, createdAt: $createdAt)';
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) return true;
-
-    return other is User &&
-        other.id == id &&
-        other.email == email &&
-        other.createdAt == createdAt &&
-        other.firstName == firstName &&
-        other.lastName == lastName;
-  }
-
-  @override
-  int get hashCode => id.hashCode ^ email.hashCode ^ createdAt.hashCode;
-}
diff --git a/mobile/lib/shared/providers/asset.provider.dart b/mobile/lib/shared/providers/asset.provider.dart
index bb42fbd1eb..84ddc2dc98 100644
--- a/mobile/lib/shared/providers/asset.provider.dart
+++ b/mobile/lib/shared/providers/asset.provider.dart
@@ -1,21 +1,20 @@
 import 'package:flutter/foundation.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/home/models/delete_asset_response.model.dart';
 import 'package:immich_mobile/modules/home/services/asset.service.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:immich_mobile/shared/services/device_info.service.dart';
 import 'package:collection/collection.dart';
 import 'package:intl/intl.dart';
+import 'package:openapi/api.dart';
 import 'package:photo_manager/photo_manager.dart';
 
-class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
+class AssetNotifier extends StateNotifier<List<AssetResponseDto>> {
   final AssetService _assetService;
   final DeviceInfoService _deviceInfoService = DeviceInfoService();
 
   AssetNotifier(this._assetService) : super([]);
 
   getAllAsset() async {
-    List<ImmichAsset>? allAssets = await _assetService.getAllAsset();
+    var allAssets = await _assetService.getAllAsset();
 
     if (allAssets != null) {
       state = allAssets;
@@ -26,11 +25,11 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
     state = [];
   }
 
-  onNewAssetUploaded(ImmichAsset newAsset) {
+  onNewAssetUploaded(AssetResponseDto newAsset) {
     state = [...state, newAsset];
   }
 
-  deleteAssets(Set<ImmichAsset> deleteAssets) async {
+  deleteAssets(Set<AssetResponseDto> deleteAssets) async {
     var deviceInfo = await _deviceInfoService.getDeviceInfo();
     var deviceId = deviceInfo["deviceId"];
     var deleteIdList = <String>[];
@@ -53,14 +52,15 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
     }
 
     // Delete asset on server
-    List<DeleteAssetResponse>? deleteAssetResult =
+    List<DeleteAssetResponseDto>? deleteAssetResult =
         await _assetService.deleteAssets(deleteAssets);
+
     if (deleteAssetResult == null) {
       return;
     }
 
     for (var asset in deleteAssetResult) {
-      if (asset.status == 'success') {
+      if (asset.status == DeleteAssetStatus.SUCCESS) {
         state =
             state.where((immichAsset) => immichAsset.id != asset.id).toList();
       }
@@ -69,7 +69,7 @@ class AssetNotifier extends StateNotifier<List<ImmichAsset>> {
 }
 
 final assetProvider =
-    StateNotifierProvider<AssetNotifier, List<ImmichAsset>>((ref) {
+    StateNotifierProvider<AssetNotifier, List<AssetResponseDto>>((ref) {
   return AssetNotifier(ref.watch(assetServiceProvider));
 });
 
@@ -77,17 +77,25 @@ final assetGroupByDateTimeProvider = StateProvider((ref) {
   var assets = ref.watch(assetProvider);
 
   assets.sortByCompare<DateTime>(
-      (e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
-  return assets.groupListsBy((element) =>
-      DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
+    (e) => DateTime.parse(e.createdAt),
+    (a, b) => b.compareTo(a),
+  );
+  return assets.groupListsBy(
+    (element) =>
+        DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)),
+  );
 });
 
 final assetGroupByMonthYearProvider = StateProvider((ref) {
   var assets = ref.watch(assetProvider);
 
   assets.sortByCompare<DateTime>(
-      (e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
+    (e) => DateTime.parse(e.createdAt),
+    (a, b) => b.compareTo(a),
+  );
 
-  return assets.groupListsBy((element) =>
-      DateFormat('MMMM, y').format(DateTime.parse(element.createdAt)));
+  return assets.groupListsBy(
+    (element) =>
+        DateFormat('MMMM, y').format(DateTime.parse(element.createdAt)),
+  );
 });
diff --git a/mobile/lib/shared/providers/release_info.provider.dart b/mobile/lib/shared/providers/release_info.provider.dart
index 65276dd737..c9f91ec847 100644
--- a/mobile/lib/shared/providers/release_info.provider.dart
+++ b/mobile/lib/shared/providers/release_info.provider.dart
@@ -56,4 +56,5 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
 }
 
 final releaseInfoProvider = StateNotifierProvider<ReleaseInfoNotifier, String>(
-    (ref) => ReleaseInfoNotifier());
+  (ref) => ReleaseInfoNotifier(),
+);
diff --git a/mobile/lib/shared/providers/server_info.provider.dart b/mobile/lib/shared/providers/server_info.provider.dart
index 2eb2eedfc9..0794e73cd3 100644
--- a/mobile/lib/shared/providers/server_info.provider.dart
+++ b/mobile/lib/shared/providers/server_info.provider.dart
@@ -1,18 +1,20 @@
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 
-import 'package:immich_mobile/shared/models/mapbox_info.model.dart';
 import 'package:immich_mobile/shared/models/server_info_state.model.dart';
-import 'package:immich_mobile/shared/models/server_version.model.dart';
 import 'package:immich_mobile/shared/services/server_info.service.dart';
+import 'package:openapi/api.dart';
 import 'package:package_info_plus/package_info_plus.dart';
 
 class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
   ServerInfoNotifier(this._serverInfoService)
       : super(
           ServerInfoState(
-            mapboxInfo: MapboxInfo(isEnable: false, mapboxSecret: ""),
-            serverVersion:
-                ServerVersion(major: 0, patch: 0, minor: 0, build: 0),
+            serverVersion: ServerVersionReponseDto(
+              major: 0,
+              patch_: 0,
+              minor: 0,
+              build: 0,
+            ),
             isVersionMismatch: false,
             versionMismatchErrorMessage: "",
           ),
@@ -21,7 +23,8 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
   final ServerInfoService _serverInfoService;
 
   getServerVersion() async {
-    ServerVersion? serverVersion = await _serverInfoService.getServerVersion();
+    ServerVersionReponseDto? serverVersion =
+        await _serverInfoService.getServerVersion();
 
     if (serverVersion == null) {
       state = state.copyWith(
@@ -59,7 +62,9 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
     }
 
     state = state.copyWith(
-        isVersionMismatch: false, versionMismatchErrorMessage: "");
+      isVersionMismatch: false,
+      versionMismatchErrorMessage: "",
+    );
   }
 
   Map<String, int> _getDetailVersion(String version) {
diff --git a/mobile/lib/shared/providers/websocket.provider.dart b/mobile/lib/shared/providers/websocket.provider.dart
index 60d13f76d1..bc48762768 100644
--- a/mobile/lib/shared/providers/websocket.provider.dart
+++ b/mobile/lib/shared/providers/websocket.provider.dart
@@ -5,8 +5,8 @@ import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
-import 'package:immich_mobile/shared/models/immich_asset.model.dart';
 import 'package:immich_mobile/shared/providers/asset.provider.dart';
+import 'package:openapi/api.dart';
 import 'package:socket_io_client/socket_io_client.dart';
 
 class WebscoketState {
@@ -92,8 +92,11 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
 
         socket.on('on_upload_success', (data) {
           var jsonString = jsonDecode(data.toString());
-          ImmichAsset newAsset = ImmichAsset.fromMap(jsonString);
-          ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
+          AssetResponseDto? newAsset = AssetResponseDto.fromJson(jsonString);
+
+          if (newAsset != null) {
+            ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
+          }
         });
       } catch (e) {
         debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
@@ -119,8 +122,11 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
     debugPrint("[Websocket] Start listening to event on_upload_success");
     state.socket?.on('on_upload_success', (data) {
       var jsonString = jsonDecode(data.toString());
-      ImmichAsset newAsset = ImmichAsset.fromMap(jsonString);
-      ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
+      AssetResponseDto? newAsset = AssetResponseDto.fromJson(jsonString);
+
+      if (newAsset != null) {
+        ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
+      }
     });
   }
 }
diff --git a/mobile/lib/shared/services/api.service.dart b/mobile/lib/shared/services/api.service.dart
new file mode 100644
index 0000000000..37630beb9c
--- /dev/null
+++ b/mobile/lib/shared/services/api.service.dart
@@ -0,0 +1,30 @@
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:openapi/api.dart';
+
+final apiServiceProvider = Provider((ref) => ApiService());
+
+class ApiService {
+  late ApiClient _apiClient;
+
+  late UserApi userApi;
+  late AuthenticationApi authenticationApi;
+  late AlbumApi albumApi;
+  late AssetApi assetApi;
+  late ServerInfoApi serverInfoApi;
+  late DeviceInfoApi deviceInfoApi;
+
+  setEndpoint(String endpoint) {
+    _apiClient = ApiClient(basePath: endpoint);
+
+    userApi = UserApi(_apiClient);
+    authenticationApi = AuthenticationApi(_apiClient);
+    albumApi = AlbumApi(_apiClient);
+    assetApi = AssetApi(_apiClient);
+    serverInfoApi = ServerInfoApi(_apiClient);
+    deviceInfoApi = DeviceInfoApi(_apiClient);
+  }
+
+  setAccessToken(String accessToken) {
+    _apiClient.addDefaultHeader('Authorization', 'bearer $accessToken');
+  }
+}
diff --git a/mobile/lib/shared/services/device_info.service.dart b/mobile/lib/shared/services/device_info.service.dart
index e1cd8a91fe..429d927fc8 100644
--- a/mobile/lib/shared/services/device_info.service.dart
+++ b/mobile/lib/shared/services/device_info.service.dart
@@ -2,6 +2,7 @@ import 'package:flutter_udid/flutter_udid.dart';
 import 'dart:io' show Platform;
 
 import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:openapi/api.dart';
 
 final deviceInfoServiceProvider = Provider((_) => DeviceInfoService());
 
@@ -9,12 +10,12 @@ class DeviceInfoService {
   Future<Map<String, dynamic>> getDeviceInfo() async {
     // Get device info
     var deviceId = await FlutterUdid.consistentUdid;
-    var deviceType = "";
+    var deviceType = DeviceTypeEnum.ANDROID;
 
     if (Platform.isAndroid) {
-      deviceType = "ANDROID";
+      deviceType = DeviceTypeEnum.ANDROID;
     } else if (Platform.isIOS) {
-      deviceType = "IOS";
+      deviceType = DeviceTypeEnum.IOS;
     }
 
     return {"deviceId": deviceId, "deviceType": deviceType};
diff --git a/mobile/lib/shared/services/local_storage.service.dart b/mobile/lib/shared/services/local_storage.service.dart
deleted file mode 100644
index f409b2d7c0..0000000000
--- a/mobile/lib/shared/services/local_storage.service.dart
+++ /dev/null
@@ -1,21 +0,0 @@
-import 'package:hive/hive.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/constants/hive_box.dart';
-
-final localStorageServiceProvider = Provider((_) => LocalStorageService());
-
-class LocalStorageService {
-  late Box _box;
-
-  LocalStorageService() {
-    _box = Hive.box(userInfoBox);
-  }
-
-  T get<T>(String key) {
-    return _box.get(key);
-  }
-
-  put<T>(String key, T value) {
-    return _box.put(key, value);
-  }
-}
diff --git a/mobile/lib/shared/services/network.service.dart b/mobile/lib/shared/services/network.service.dart
index 24ad7649de..f8a502d0a1 100644
--- a/mobile/lib/shared/services/network.service.dart
+++ b/mobile/lib/shared/services/network.service.dart
@@ -33,10 +33,11 @@ class NetworkService {
     }
   }
 
-  Future<dynamic> getRequest(
-      {required String url,
-      bool isByteResponse = false,
-      bool isStreamReponse = false}) async {
+  Future<dynamic> getRequest({
+    required String url,
+    bool isByteResponse = false,
+    bool isStreamReponse = false,
+  }) async {
     try {
       var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
 
diff --git a/mobile/lib/shared/services/server_info.service.dart b/mobile/lib/shared/services/server_info.service.dart
index df25fe256e..92ea9d89d4 100644
--- a/mobile/lib/shared/services/server_info.service.dart
+++ b/mobile/lib/shared/services/server_info.service.dart
@@ -1,33 +1,33 @@
-import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/shared/models/server_info.model.dart';
-import 'package:immich_mobile/shared/models/server_version.model.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
+import 'package:openapi/api.dart';
 
-final serverInfoServiceProvider =
-    Provider((ref) => ServerInfoService(ref.watch(networkServiceProvider)));
+final serverInfoServiceProvider = Provider(
+  (ref) => ServerInfoService(
+    ref.watch(apiServiceProvider),
+  ),
+);
 
 class ServerInfoService {
-  final NetworkService _networkService;
-  ServerInfoService(this._networkService);
+  final ApiService _apiService;
+  ServerInfoService(this._apiService);
 
-  Future<ServerInfo> getServerInfo() async {
-    Response response = await _networkService.getRequest(url: 'server-info');
-
-    return ServerInfo.fromJson(response.toString());
+  Future<ServerInfoResponseDto?> getServerInfo() async {
+    try {
+      return await _apiService.serverInfoApi.getServerInfo();
+    } catch (e) {
+      debugPrint("Error [getServerInfo] ${e.toString()}");
+      return null;
+    }
   }
 
-  Future<ServerVersion?> getServerVersion() async {
+  Future<ServerVersionReponseDto?> getServerVersion() async {
     try {
-      Response response =
-          await _networkService.getRequest(url: 'server-info/version');
-
-      return ServerVersion.fromJson(response.toString());
+      return await _apiService.serverInfoApi.getServerVersion();
     } catch (e) {
       debugPrint("Error getting server info");
+      return null;
     }
-
-    return null;
   }
 }
diff --git a/mobile/lib/shared/services/user.service.dart b/mobile/lib/shared/services/user.service.dart
index 737cd97fc4..482f08635f 100644
--- a/mobile/lib/shared/services/user.service.dart
+++ b/mobile/lib/shared/services/user.service.dart
@@ -1,70 +1,49 @@
-import 'dart:convert';
-
-import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
-import 'package:hive/hive.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:http/http.dart';
 import 'package:http_parser/http_parser.dart';
 import 'package:image_picker/image_picker.dart';
-import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/shared/models/upload_profile_image_repsonse.model.dart';
-import 'package:immich_mobile/shared/models/user.model.dart';
-import 'package:immich_mobile/shared/services/network.service.dart';
-import 'package:immich_mobile/utils/dio_http_interceptor.dart';
+import 'package:immich_mobile/shared/services/api.service.dart';
 import 'package:immich_mobile/utils/files_helper.dart';
+import 'package:openapi/api.dart';
 
-final userServiceProvider =
-    Provider((ref) => UserService(ref.watch(networkServiceProvider)));
+final userServiceProvider = Provider(
+  (ref) => UserService(
+    ref.watch(apiServiceProvider),
+  ),
+);
 
 class UserService {
-  final NetworkService _networkService;
-  UserService(this._networkService);
+  final ApiService _apiService;
 
-  Future<List<User>> getAllUsersInfo() async {
+  UserService(this._apiService);
+
+  Future<List<UserResponseDto>?> getAllUsersInfo({required bool isAll}) async {
     try {
-      var res = await _networkService.getRequest(url: 'user');
-      List<dynamic> decodedData = jsonDecode(res.toString());
-      List<User> result = List.from(decodedData.map((e) => User.fromMap(e)));
-
-      return result;
+      return await _apiService.userApi.getAllUsers(isAll);
     } catch (e) {
-      debugPrint("Error getAllUsersInfo  ${e.toString()}");
+      debugPrint("Error [getAllUsersInfo]  ${e.toString()}");
+      return null;
     }
-
-    return [];
   }
 
-  Future<UploadProfileImageResponse?> uploadProfileImage(XFile image) async {
-    var dio = Dio();
-    dio.interceptors.add(AuthenticatedRequestInterceptor());
-    String savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
-    var mimeType = FileHelper.getMimeType(image.path);
-
-    final imageData = MultipartFile.fromBytes(
-      await image.readAsBytes(),
-      filename: image.name,
-      contentType: MediaType(
-        mimeType["type"],
-        mimeType["subType"],
-      ),
-    );
-
-    final formData = FormData.fromMap({'file': imageData});
-
+  Future<CreateProfileImageResponseDto?> uploadProfileImage(XFile image) async {
     try {
-      Response res = await dio.post(
-        '$savedEndpoint/user/profile-image',
-        data: formData,
+      var mimeType = FileHelper.getMimeType(image.path);
+
+      return await _apiService.userApi.createProfileImage(
+        MultipartFile.fromBytes(
+          'file',
+          await image.readAsBytes(),
+          filename: image.name,
+          contentType: MediaType(
+            mimeType["type"],
+            mimeType["subType"],
+          ),
+        ),
       );
-
-      var payload = UploadProfileImageResponse.fromJson(res.toString());
-
-      return payload;
-    } on DioError catch (e) {
-      debugPrint("Error uploading file: ${e.response}");
-      return null;
     } catch (e) {
-      debugPrint("Error uploading file: $e");
+      debugPrint("Error [uploadProfileImage] ${e.toString()}");
       return null;
     }
   }
diff --git a/mobile/lib/shared/ui/immich_sliver_persistent_app_bar_delegate.dart b/mobile/lib/shared/ui/immich_sliver_persistent_app_bar_delegate.dart
index 85a67daa2c..d9c43cd353 100644
--- a/mobile/lib/shared/ui/immich_sliver_persistent_app_bar_delegate.dart
+++ b/mobile/lib/shared/ui/immich_sliver_persistent_app_bar_delegate.dart
@@ -22,7 +22,10 @@ class ImmichSliverPersistentAppBarDelegate
 
   @override
   Widget build(
-      BuildContext context, double shrinkOffset, bool overlapsContent) {
+    BuildContext context,
+    double shrinkOffset,
+    bool overlapsContent,
+  ) {
     return SizedBox.expand(child: child);
   }
 
diff --git a/mobile/lib/shared/views/splash_screen.dart b/mobile/lib/shared/views/splash_screen.dart
index fd3828ee4a..23a5456023 100644
--- a/mobile/lib/shared/views/splash_screen.dart
+++ b/mobile/lib/shared/views/splash_screen.dart
@@ -19,10 +19,13 @@ class SplashScreenPage extends HookConsumerWidget {
         Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).get(savedLoginInfoKey);
 
     void performLoggingIn() async {
-      var isAuthenticated = await ref
-          .read(authenticationProvider.notifier)
-          .login(
-              loginInfo!.email, loginInfo.password, loginInfo.serverUrl, true);
+      var isAuthenticated =
+          await ref.read(authenticationProvider.notifier).login(
+                loginInfo!.email,
+                loginInfo.password,
+                loginInfo.serverUrl,
+                true,
+              );
 
       if (isAuthenticated) {
         // Resume backup (if enable) then navigate
@@ -33,14 +36,17 @@ class SplashScreenPage extends HookConsumerWidget {
       }
     }
 
-    useEffect(() {
-      if (loginInfo?.isSaveLogin == true) {
-        performLoggingIn();
-      } else {
-        AutoRouter.of(context).push(const LoginRoute());
-      }
-      return null;
-    }, []);
+    useEffect(
+      () {
+        if (loginInfo?.isSaveLogin == true) {
+          performLoggingIn();
+        } else {
+          AutoRouter.of(context).push(const LoginRoute());
+        }
+        return null;
+      },
+      [],
+    );
 
     return Scaffold(
       backgroundColor: immichBackgroundColor,
diff --git a/mobile/lib/shared/views/tab_controller_page.dart b/mobile/lib/shared/views/tab_controller_page.dart
index b32baf33bf..9da202f089 100644
--- a/mobile/lib/shared/views/tab_controller_page.dart
+++ b/mobile/lib/shared/views/tab_controller_page.dart
@@ -35,23 +35,30 @@ class TabControllerPage extends ConsumerWidget {
                 ? null
                 : BottomNavigationBar(
                     selectedLabelStyle: const TextStyle(
-                        fontSize: 15, fontWeight: FontWeight.w600),
+                      fontSize: 15,
+                      fontWeight: FontWeight.w600,
+                    ),
                     unselectedLabelStyle: const TextStyle(
-                        fontSize: 15, fontWeight: FontWeight.w600),
+                      fontSize: 15,
+                      fontWeight: FontWeight.w600,
+                    ),
                     currentIndex: tabsRouter.activeIndex,
                     onTap: (index) {
                       tabsRouter.setActiveIndex(index);
                     },
                     items: [
                       BottomNavigationBarItem(
-                          label: 'tab_controller_nav_photos'.tr(),
-                          icon: const Icon(Icons.photo)),
+                        label: 'tab_controller_nav_photos'.tr(),
+                        icon: const Icon(Icons.photo),
+                      ),
                       BottomNavigationBarItem(
-                          label: 'tab_controller_nav_search'.tr(),
-                          icon: const Icon(Icons.search)),
+                        label: 'tab_controller_nav_search'.tr(),
+                        icon: const Icon(Icons.search),
+                      ),
                       BottomNavigationBarItem(
-                          label: 'tab_controller_nav_sharing'.tr(),
-                          icon: const Icon(Icons.group_outlined)),
+                        label: 'tab_controller_nav_sharing'.tr(),
+                        icon: const Icon(Icons.group_outlined),
+                      ),
                     ],
                   ),
           ),
diff --git a/mobile/lib/shared/views/version_announcement_overlay.dart b/mobile/lib/shared/views/version_announcement_overlay.dart
index 90095ef27f..aa56bd8fbd 100644
--- a/mobile/lib/shared/views/version_announcement_overlay.dart
+++ b/mobile/lib/shared/views/version_announcement_overlay.dart
@@ -54,13 +54,16 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
                               child: RichText(
                                 text: TextSpan(
                                   style: const TextStyle(
-                                      fontSize: 14,
-                                      fontFamily: 'WorkSans',
-                                      color: Colors.black87,
-                                      height: 1.2),
+                                    fontSize: 14,
+                                    fontFamily: 'WorkSans',
+                                    color: Colors.black87,
+                                    height: 1.2,
+                                  ),
                                   children: <TextSpan>[
                                     TextSpan(
-                                      text: 'version_announcement_overlay_text_1'.tr(),
+                                      text:
+                                          'version_announcement_overlay_text_1'
+                                              .tr(),
                                     ),
                                     const TextSpan(
                                       text: ' Immich ',
@@ -71,11 +74,14 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
                                       ),
                                     ),
                                     TextSpan(
-                                      text: "version_announcement_overlay_text_2".tr(),
+                                      text:
+                                          "version_announcement_overlay_text_2"
+                                              .tr(),
                                     ),
                                     TextSpan(
-                                      text: "version_announcement_overlay_release_notes"
-                                          .tr(),
+                                      text:
+                                          "version_announcement_overlay_release_notes"
+                                              .tr(),
                                       style: const TextStyle(
                                         decoration: TextDecoration.underline,
                                       ),
@@ -83,7 +89,9 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
                                         ..onTap = goToReleaseNote,
                                     ),
                                     TextSpan(
-                                      text: "version_announcement_overlay_text_3".tr(),
+                                      text:
+                                          "version_announcement_overlay_text_3"
+                                              .tr(),
                                     )
                                   ],
                                 ),
@@ -92,22 +100,25 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
                             Padding(
                               padding: const EdgeInsets.only(top: 16.0),
                               child: ElevatedButton(
-                                  style: ElevatedButton.styleFrom(
-                                    shape: const StadiumBorder(),
-                                    visualDensity: VisualDensity.standard,
-                                    primary: Colors.indigo,
-                                    onPrimary: Colors.grey[50],
-                                    elevation: 2,
-                                    padding: const EdgeInsets.symmetric(
-                                        vertical: 10, horizontal: 25),
+                                style: ElevatedButton.styleFrom(
+                                  shape: const StadiumBorder(),
+                                  visualDensity: VisualDensity.standard,
+                                  primary: Colors.indigo,
+                                  onPrimary: Colors.grey[50],
+                                  elevation: 2,
+                                  padding: const EdgeInsets.symmetric(
+                                    vertical: 10,
+                                    horizontal: 25,
                                   ),
-                                  onPressed: onAcknowledgeTapped,
-                                  child: const Text(
-                                    "version_announcement_overlay_ack",
-                                    style: TextStyle(
-                                      fontSize: 14,
-                                    ),
-                                  ).tr()),
+                                ),
+                                onPressed: onAcknowledgeTapped,
+                                child: const Text(
+                                  "version_announcement_overlay_ack",
+                                  style: TextStyle(
+                                    fontSize: 14,
+                                  ),
+                                ).tr(),
+                              ),
                             )
                           ],
                         ),
diff --git a/mobile/openapi/.gitignore b/mobile/openapi/.gitignore
new file mode 100644
index 0000000000..1be28ced09
--- /dev/null
+++ b/mobile/openapi/.gitignore
@@ -0,0 +1,17 @@
+# See https://dart.dev/guides/libraries/private-files
+
+.dart_tool/
+.packages
+build/
+pubspec.lock  # Except for application packages
+
+doc/api/
+
+# IntelliJ
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# Mac
+.DS_Store
diff --git a/mobile/openapi/.openapi-generator-ignore b/mobile/openapi/.openapi-generator-ignore
new file mode 100644
index 0000000000..7484ee590a
--- /dev/null
+++ b/mobile/openapi/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES
new file mode 100644
index 0000000000..9818927be1
--- /dev/null
+++ b/mobile/openapi/.openapi-generator/FILES
@@ -0,0 +1,100 @@
+.gitignore
+.travis.yml
+README.md
+analysis_options.yaml
+doc/AddAssetsDto.md
+doc/AddUsersDto.md
+doc/AdminSignupResponseDto.md
+doc/AlbumApi.md
+doc/AlbumResponseDto.md
+doc/AssetApi.md
+doc/AssetFileUploadResponseDto.md
+doc/AssetResponseDto.md
+doc/AssetTypeEnum.md
+doc/AuthenticationApi.md
+doc/CheckDuplicateAssetDto.md
+doc/CheckDuplicateAssetResponseDto.md
+doc/CreateAlbumDto.md
+doc/CreateDeviceInfoDto.md
+doc/CreateProfileImageResponseDto.md
+doc/CreateUserDto.md
+doc/CuratedLocationsResponseDto.md
+doc/CuratedObjectsResponseDto.md
+doc/DeleteAssetDto.md
+doc/DeleteAssetResponseDto.md
+doc/DeleteAssetStatus.md
+doc/DeviceInfoApi.md
+doc/DeviceInfoResponseDto.md
+doc/DeviceTypeEnum.md
+doc/ExifResponseDto.md
+doc/LoginCredentialDto.md
+doc/LoginResponseDto.md
+doc/RemoveAssetsDto.md
+doc/SearchAssetDto.md
+doc/ServerInfoApi.md
+doc/ServerInfoResponseDto.md
+doc/ServerPingResponse.md
+doc/ServerVersionReponseDto.md
+doc/SignUpDto.md
+doc/SmartInfoResponseDto.md
+doc/UpdateAlbumDto.md
+doc/UpdateDeviceInfoDto.md
+doc/UpdateUserDto.md
+doc/UserApi.md
+doc/UserCountResponseDto.md
+doc/UserResponseDto.md
+doc/ValidateAccessTokenResponseDto.md
+git_push.sh
+lib/api.dart
+lib/api/album_api.dart
+lib/api/asset_api.dart
+lib/api/authentication_api.dart
+lib/api/device_info_api.dart
+lib/api/server_info_api.dart
+lib/api/user_api.dart
+lib/api_client.dart
+lib/api_exception.dart
+lib/api_helper.dart
+lib/auth/api_key_auth.dart
+lib/auth/authentication.dart
+lib/auth/http_basic_auth.dart
+lib/auth/http_bearer_auth.dart
+lib/auth/oauth.dart
+lib/model/add_assets_dto.dart
+lib/model/add_users_dto.dart
+lib/model/admin_signup_response_dto.dart
+lib/model/album_response_dto.dart
+lib/model/asset_file_upload_response_dto.dart
+lib/model/asset_response_dto.dart
+lib/model/asset_type_enum.dart
+lib/model/check_duplicate_asset_dto.dart
+lib/model/check_duplicate_asset_response_dto.dart
+lib/model/create_album_dto.dart
+lib/model/create_device_info_dto.dart
+lib/model/create_profile_image_response_dto.dart
+lib/model/create_user_dto.dart
+lib/model/curated_locations_response_dto.dart
+lib/model/curated_objects_response_dto.dart
+lib/model/delete_asset_dto.dart
+lib/model/delete_asset_response_dto.dart
+lib/model/delete_asset_status.dart
+lib/model/device_info_response_dto.dart
+lib/model/device_type_enum.dart
+lib/model/exif_response_dto.dart
+lib/model/login_credential_dto.dart
+lib/model/login_response_dto.dart
+lib/model/remove_assets_dto.dart
+lib/model/search_asset_dto.dart
+lib/model/server_info_response_dto.dart
+lib/model/server_ping_response.dart
+lib/model/server_version_reponse_dto.dart
+lib/model/sign_up_dto.dart
+lib/model/smart_info_response_dto.dart
+lib/model/update_album_dto.dart
+lib/model/update_device_info_dto.dart
+lib/model/update_user_dto.dart
+lib/model/user_count_response_dto.dart
+lib/model/user_response_dto.dart
+lib/model/validate_access_token_response_dto.dart
+pubspec.yaml
+test/validate_access_token_response_dto_test.dart
diff --git a/mobile/openapi/.openapi-generator/VERSION b/mobile/openapi/.openapi-generator/VERSION
new file mode 100644
index 0000000000..6d54bbd775
--- /dev/null
+++ b/mobile/openapi/.openapi-generator/VERSION
@@ -0,0 +1 @@
+6.0.1
\ No newline at end of file
diff --git a/mobile/openapi/.travis.yml b/mobile/openapi/.travis.yml
new file mode 100644
index 0000000000..2774ccbba0
--- /dev/null
+++ b/mobile/openapi/.travis.yml
@@ -0,0 +1,14 @@
+#
+# AUTO-GENERATED FILE, DO NOT MODIFY!
+#
+# https://docs.travis-ci.com/user/languages/dart/
+#
+language: dart
+dart:
+# Install a specific stable release
+- "2.12"
+install:
+- pub get
+
+script:
+- pub run test
diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md
new file mode 100644
index 0000000000..1e9edfc0a0
--- /dev/null
+++ b/mobile/openapi/README.md
@@ -0,0 +1,158 @@
+# openapi
+Immich API
+
+This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
+
+- API version: 1.17.0
+- Build package: org.openapitools.codegen.languages.DartClientCodegen
+
+## Requirements
+
+Dart 2.12 or later
+
+## Installation & Usage
+
+### Github
+If this Dart package is published to Github, add the following dependency to your pubspec.yaml
+```
+dependencies:
+  openapi:
+    git: https://github.com/GIT_USER_ID/GIT_REPO_ID.git
+```
+
+### Local
+To use the package in your local drive, add the following dependency to your pubspec.yaml
+```
+dependencies:
+  openapi:
+    path: /path/to/openapi
+```
+
+## Tests
+
+TODO
+
+## Getting Started
+
+Please follow the [installation procedure](#installation--usage) and then run the following:
+
+```dart
+import 'package:openapi/api.dart';
+
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+final addAssetsDto = AddAssetsDto(); // AddAssetsDto | 
+
+try {
+    final result = api_instance.addAssetsToAlbum(albumId, addAssetsDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AlbumApi->addAssetsToAlbum: $e\n');
+}
+
+```
+
+## Documentation for API Endpoints
+
+All URIs are relative to */api*
+
+Class | Method | HTTP request | Description
+------------ | ------------- | ------------- | -------------
+*AlbumApi* | [**addAssetsToAlbum**](doc//AlbumApi.md#addassetstoalbum) | **PUT** /album/{albumId}/assets | 
+*AlbumApi* | [**addUsersToAlbum**](doc//AlbumApi.md#adduserstoalbum) | **PUT** /album/{albumId}/users | 
+*AlbumApi* | [**createAlbum**](doc//AlbumApi.md#createalbum) | **POST** /album | 
+*AlbumApi* | [**deleteAlbum**](doc//AlbumApi.md#deletealbum) | **DELETE** /album/{albumId} | 
+*AlbumApi* | [**getAlbumInfo**](doc//AlbumApi.md#getalbuminfo) | **GET** /album/{albumId} | 
+*AlbumApi* | [**getAllAlbums**](doc//AlbumApi.md#getallalbums) | **GET** /album | 
+*AlbumApi* | [**removeAssetFromAlbum**](doc//AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{albumId}/assets | 
+*AlbumApi* | [**removeUserFromAlbum**](doc//AlbumApi.md#removeuserfromalbum) | **DELETE** /album/{albumId}/user/{userId} | 
+*AlbumApi* | [**updateAlbumInfo**](doc//AlbumApi.md#updatealbuminfo) | **PATCH** /album/{albumId} | 
+*AssetApi* | [**checkDuplicateAsset**](doc//AssetApi.md#checkduplicateasset) | **POST** /asset/check | 
+*AssetApi* | [**deleteAsset**](doc//AssetApi.md#deleteasset) | **DELETE** /asset | 
+*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download | 
+*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset | 
+*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} | 
+*AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/searchTerm | 
+*AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} | 
+*AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/allLocation | 
+*AssetApi* | [**getCuratedObjects**](doc//AssetApi.md#getcuratedobjects) | **GET** /asset/allObjects | 
+*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} | 
+*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search | 
+*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file | 
+*AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload | 
+*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up | 
+*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login | 
+*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | 
+*DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info | 
+*DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info | 
+*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info | 
+*ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version | 
+*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping | 
+*UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image | 
+*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user | 
+*UserApi* | [**getAllUsers**](doc//UserApi.md#getallusers) | **GET** /user | 
+*UserApi* | [**getMyUserInfo**](doc//UserApi.md#getmyuserinfo) | **GET** /user/me | 
+*UserApi* | [**getProfileImage**](doc//UserApi.md#getprofileimage) | **GET** /user/profile-image/{userId} | 
+*UserApi* | [**getUserCount**](doc//UserApi.md#getusercount) | **GET** /user/count | 
+*UserApi* | [**updateUser**](doc//UserApi.md#updateuser) | **PUT** /user | 
+
+
+## Documentation For Models
+
+ - [AddAssetsDto](doc//AddAssetsDto.md)
+ - [AddUsersDto](doc//AddUsersDto.md)
+ - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
+ - [AlbumResponseDto](doc//AlbumResponseDto.md)
+ - [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
+ - [AssetResponseDto](doc//AssetResponseDto.md)
+ - [AssetTypeEnum](doc//AssetTypeEnum.md)
+ - [CheckDuplicateAssetDto](doc//CheckDuplicateAssetDto.md)
+ - [CheckDuplicateAssetResponseDto](doc//CheckDuplicateAssetResponseDto.md)
+ - [CreateAlbumDto](doc//CreateAlbumDto.md)
+ - [CreateDeviceInfoDto](doc//CreateDeviceInfoDto.md)
+ - [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
+ - [CreateUserDto](doc//CreateUserDto.md)
+ - [CuratedLocationsResponseDto](doc//CuratedLocationsResponseDto.md)
+ - [CuratedObjectsResponseDto](doc//CuratedObjectsResponseDto.md)
+ - [DeleteAssetDto](doc//DeleteAssetDto.md)
+ - [DeleteAssetResponseDto](doc//DeleteAssetResponseDto.md)
+ - [DeleteAssetStatus](doc//DeleteAssetStatus.md)
+ - [DeviceInfoResponseDto](doc//DeviceInfoResponseDto.md)
+ - [DeviceTypeEnum](doc//DeviceTypeEnum.md)
+ - [ExifResponseDto](doc//ExifResponseDto.md)
+ - [LoginCredentialDto](doc//LoginCredentialDto.md)
+ - [LoginResponseDto](doc//LoginResponseDto.md)
+ - [RemoveAssetsDto](doc//RemoveAssetsDto.md)
+ - [SearchAssetDto](doc//SearchAssetDto.md)
+ - [ServerInfoResponseDto](doc//ServerInfoResponseDto.md)
+ - [ServerPingResponse](doc//ServerPingResponse.md)
+ - [ServerVersionReponseDto](doc//ServerVersionReponseDto.md)
+ - [SignUpDto](doc//SignUpDto.md)
+ - [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
+ - [UpdateAlbumDto](doc//UpdateAlbumDto.md)
+ - [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
+ - [UpdateUserDto](doc//UpdateUserDto.md)
+ - [UserCountResponseDto](doc//UserCountResponseDto.md)
+ - [UserResponseDto](doc//UserResponseDto.md)
+ - [ValidateAccessTokenResponseDto](doc//ValidateAccessTokenResponseDto.md)
+
+
+## Documentation For Authorization
+
+
+## bearer
+
+- **Type**: HTTP Bearer authentication
+
+
+## Author
+
+
+
diff --git a/mobile/openapi/analysis_options.yaml b/mobile/openapi/analysis_options.yaml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/mobile/openapi/doc/AddAssetsDto.md b/mobile/openapi/doc/AddAssetsDto.md
new file mode 100644
index 0000000000..b74211d6b7
--- /dev/null
+++ b/mobile/openapi/doc/AddAssetsDto.md
@@ -0,0 +1,15 @@
+# openapi.model.AddAssetsDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**assetIds** | **List<String>** |  | [default to const []]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/AddUsersDto.md b/mobile/openapi/doc/AddUsersDto.md
new file mode 100644
index 0000000000..9f7770d604
--- /dev/null
+++ b/mobile/openapi/doc/AddUsersDto.md
@@ -0,0 +1,15 @@
+# openapi.model.AddUsersDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**sharedUserIds** | **List<String>** |  | [default to const []]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/AdminSignupResponseDto.md b/mobile/openapi/doc/AdminSignupResponseDto.md
new file mode 100644
index 0000000000..ff9890de5d
--- /dev/null
+++ b/mobile/openapi/doc/AdminSignupResponseDto.md
@@ -0,0 +1,19 @@
+# openapi.model.AdminSignupResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | 
+**email** | **String** |  | 
+**firstName** | **String** |  | 
+**lastName** | **String** |  | 
+**createdAt** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/AlbumApi.md b/mobile/openapi/doc/AlbumApi.md
new file mode 100644
index 0000000000..1a0fe61ed4
--- /dev/null
+++ b/mobile/openapi/doc/AlbumApi.md
@@ -0,0 +1,452 @@
+# openapi.api.AlbumApi
+
+## Load the API package
+```dart
+import 'package:openapi/api.dart';
+```
+
+All URIs are relative to */api*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+[**addAssetsToAlbum**](AlbumApi.md#addassetstoalbum) | **PUT** /album/{albumId}/assets | 
+[**addUsersToAlbum**](AlbumApi.md#adduserstoalbum) | **PUT** /album/{albumId}/users | 
+[**createAlbum**](AlbumApi.md#createalbum) | **POST** /album | 
+[**deleteAlbum**](AlbumApi.md#deletealbum) | **DELETE** /album/{albumId} | 
+[**getAlbumInfo**](AlbumApi.md#getalbuminfo) | **GET** /album/{albumId} | 
+[**getAllAlbums**](AlbumApi.md#getallalbums) | **GET** /album | 
+[**removeAssetFromAlbum**](AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{albumId}/assets | 
+[**removeUserFromAlbum**](AlbumApi.md#removeuserfromalbum) | **DELETE** /album/{albumId}/user/{userId} | 
+[**updateAlbumInfo**](AlbumApi.md#updatealbuminfo) | **PATCH** /album/{albumId} | 
+
+
+# **addAssetsToAlbum**
+> AlbumResponseDto addAssetsToAlbum(albumId, addAssetsDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+final addAssetsDto = AddAssetsDto(); // AddAssetsDto | 
+
+try {
+    final result = api_instance.addAssetsToAlbum(albumId, addAssetsDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AlbumApi->addAssetsToAlbum: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **albumId** | **String**|  | 
+ **addAssetsDto** | [**AddAssetsDto**](AddAssetsDto.md)|  | 
+
+### Return type
+
+[**AlbumResponseDto**](AlbumResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **addUsersToAlbum**
+> AlbumResponseDto addUsersToAlbum(albumId, addUsersDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+final addUsersDto = AddUsersDto(); // AddUsersDto | 
+
+try {
+    final result = api_instance.addUsersToAlbum(albumId, addUsersDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AlbumApi->addUsersToAlbum: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **albumId** | **String**|  | 
+ **addUsersDto** | [**AddUsersDto**](AddUsersDto.md)|  | 
+
+### Return type
+
+[**AlbumResponseDto**](AlbumResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **createAlbum**
+> AlbumResponseDto createAlbum(createAlbumDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final createAlbumDto = CreateAlbumDto(); // CreateAlbumDto | 
+
+try {
+    final result = api_instance.createAlbum(createAlbumDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AlbumApi->createAlbum: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **createAlbumDto** | [**CreateAlbumDto**](CreateAlbumDto.md)|  | 
+
+### Return type
+
+[**AlbumResponseDto**](AlbumResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **deleteAlbum**
+> deleteAlbum(albumId)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+
+try {
+    api_instance.deleteAlbum(albumId);
+} catch (e) {
+    print('Exception when calling AlbumApi->deleteAlbum: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **albumId** | **String**|  | 
+
+### Return type
+
+void (empty response body)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: Not defined
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getAlbumInfo**
+> AlbumResponseDto getAlbumInfo(albumId)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+
+try {
+    final result = api_instance.getAlbumInfo(albumId);
+    print(result);
+} catch (e) {
+    print('Exception when calling AlbumApi->getAlbumInfo: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **albumId** | **String**|  | 
+
+### Return type
+
+[**AlbumResponseDto**](AlbumResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getAllAlbums**
+> List<AlbumResponseDto> getAllAlbums(shared)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final shared = true; // bool | 
+
+try {
+    final result = api_instance.getAllAlbums(shared);
+    print(result);
+} catch (e) {
+    print('Exception when calling AlbumApi->getAllAlbums: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **shared** | **bool**|  | [optional] 
+
+### Return type
+
+[**List<AlbumResponseDto>**](AlbumResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **removeAssetFromAlbum**
+> removeAssetFromAlbum(albumId, removeAssetsDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+final removeAssetsDto = RemoveAssetsDto(); // RemoveAssetsDto | 
+
+try {
+    api_instance.removeAssetFromAlbum(albumId, removeAssetsDto);
+} catch (e) {
+    print('Exception when calling AlbumApi->removeAssetFromAlbum: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **albumId** | **String**|  | 
+ **removeAssetsDto** | [**RemoveAssetsDto**](RemoveAssetsDto.md)|  | 
+
+### Return type
+
+void (empty response body)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: Not defined
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **removeUserFromAlbum**
+> removeUserFromAlbum(albumId, userId)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+final userId = userId_example; // String | 
+
+try {
+    api_instance.removeUserFromAlbum(albumId, userId);
+} catch (e) {
+    print('Exception when calling AlbumApi->removeUserFromAlbum: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **albumId** | **String**|  | 
+ **userId** | **String**|  | 
+
+### Return type
+
+void (empty response body)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: Not defined
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **updateAlbumInfo**
+> AlbumResponseDto updateAlbumInfo(albumId, updateAlbumDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AlbumApi();
+final albumId = albumId_example; // String | 
+final updateAlbumDto = UpdateAlbumDto(); // UpdateAlbumDto | 
+
+try {
+    final result = api_instance.updateAlbumInfo(albumId, updateAlbumDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AlbumApi->updateAlbumInfo: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **albumId** | **String**|  | 
+ **updateAlbumDto** | [**UpdateAlbumDto**](UpdateAlbumDto.md)|  | 
+
+### Return type
+
+[**AlbumResponseDto**](AlbumResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
diff --git a/mobile/openapi/doc/AlbumResponseDto.md b/mobile/openapi/doc/AlbumResponseDto.md
new file mode 100644
index 0000000000..79dfd84904
--- /dev/null
+++ b/mobile/openapi/doc/AlbumResponseDto.md
@@ -0,0 +1,22 @@
+# openapi.model.AlbumResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | 
+**ownerId** | **String** |  | 
+**albumName** | **String** |  | 
+**createdAt** | **String** |  | 
+**albumThumbnailAssetId** | **String** |  | 
+**shared** | **bool** |  | 
+**sharedUsers** | [**List<UserResponseDto>**](UserResponseDto.md) |  | [default to const []]
+**assets** | [**List<AssetResponseDto>**](AssetResponseDto.md) |  | [default to const []]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md
new file mode 100644
index 0000000000..89455a9e6a
--- /dev/null
+++ b/mobile/openapi/doc/AssetApi.md
@@ -0,0 +1,641 @@
+# openapi.api.AssetApi
+
+## Load the API package
+```dart
+import 'package:openapi/api.dart';
+```
+
+All URIs are relative to */api*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+[**checkDuplicateAsset**](AssetApi.md#checkduplicateasset) | **POST** /asset/check | 
+[**deleteAsset**](AssetApi.md#deleteasset) | **DELETE** /asset | 
+[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download | 
+[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset | 
+[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} | 
+[**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/searchTerm | 
+[**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} | 
+[**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/allLocation | 
+[**getCuratedObjects**](AssetApi.md#getcuratedobjects) | **GET** /asset/allObjects | 
+[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} | 
+[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search | 
+[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file | 
+[**uploadFile**](AssetApi.md#uploadfile) | **POST** /asset/upload | 
+
+
+# **checkDuplicateAsset**
+> CheckDuplicateAssetResponseDto checkDuplicateAsset(checkDuplicateAssetDto)
+
+
+
+Check duplicated asset before uploading - for Web upload used
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final checkDuplicateAssetDto = CheckDuplicateAssetDto(); // CheckDuplicateAssetDto | 
+
+try {
+    final result = api_instance.checkDuplicateAsset(checkDuplicateAssetDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->checkDuplicateAsset: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **checkDuplicateAssetDto** | [**CheckDuplicateAssetDto**](CheckDuplicateAssetDto.md)|  | 
+
+### Return type
+
+[**CheckDuplicateAssetResponseDto**](CheckDuplicateAssetResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **deleteAsset**
+> List<DeleteAssetResponseDto> deleteAsset(deleteAssetDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final deleteAssetDto = DeleteAssetDto(); // DeleteAssetDto | 
+
+try {
+    final result = api_instance.deleteAsset(deleteAssetDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->deleteAsset: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **deleteAssetDto** | [**DeleteAssetDto**](DeleteAssetDto.md)|  | 
+
+### Return type
+
+[**List<DeleteAssetResponseDto>**](DeleteAssetResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **downloadFile**
+> Object downloadFile(aid, did, isThumb, isWeb)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final aid = aid_example; // String | 
+final did = did_example; // String | 
+final isThumb = true; // bool | 
+final isWeb = true; // bool | 
+
+try {
+    final result = api_instance.downloadFile(aid, did, isThumb, isWeb);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->downloadFile: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **aid** | **String**|  | 
+ **did** | **String**|  | 
+ **isThumb** | **bool**|  | [optional] 
+ **isWeb** | **bool**|  | [optional] 
+
+### Return type
+
+[**Object**](Object.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getAllAssets**
+> List<AssetResponseDto> getAllAssets()
+
+
+
+Get all AssetEntity belong to the user
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+
+try {
+    final result = api_instance.getAllAssets();
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->getAllAssets: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**List<AssetResponseDto>**](AssetResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getAssetById**
+> AssetResponseDto getAssetById(assetId)
+
+
+
+Get a single asset's information
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final assetId = assetId_example; // String | 
+
+try {
+    final result = api_instance.getAssetById(assetId);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->getAssetById: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **assetId** | **String**|  | 
+
+### Return type
+
+[**AssetResponseDto**](AssetResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getAssetSearchTerms**
+> List<String> getAssetSearchTerms()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+
+try {
+    final result = api_instance.getAssetSearchTerms();
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->getAssetSearchTerms: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+**List<String>**
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getAssetThumbnail**
+> Object getAssetThumbnail(assetId)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final assetId = assetId_example; // String | 
+
+try {
+    final result = api_instance.getAssetThumbnail(assetId);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->getAssetThumbnail: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **assetId** | **String**|  | 
+
+### Return type
+
+[**Object**](Object.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getCuratedLocations**
+> List<CuratedLocationsResponseDto> getCuratedLocations()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+
+try {
+    final result = api_instance.getCuratedLocations();
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->getCuratedLocations: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**List<CuratedLocationsResponseDto>**](CuratedLocationsResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getCuratedObjects**
+> List<CuratedObjectsResponseDto> getCuratedObjects()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+
+try {
+    final result = api_instance.getCuratedObjects();
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->getCuratedObjects: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**List<CuratedObjectsResponseDto>**](CuratedObjectsResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getUserAssetsByDeviceId**
+> List<String> getUserAssetsByDeviceId(deviceId)
+
+
+
+Get all asset of a device that are in the database, ID only.
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final deviceId = deviceId_example; // String | 
+
+try {
+    final result = api_instance.getUserAssetsByDeviceId(deviceId);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->getUserAssetsByDeviceId: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **deviceId** | **String**|  | 
+
+### Return type
+
+**List<String>**
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **searchAsset**
+> List<AssetResponseDto> searchAsset(searchAssetDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final searchAssetDto = SearchAssetDto(); // SearchAssetDto | 
+
+try {
+    final result = api_instance.searchAsset(searchAssetDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->searchAsset: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **searchAssetDto** | [**SearchAssetDto**](SearchAssetDto.md)|  | 
+
+### Return type
+
+[**List<AssetResponseDto>**](AssetResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **serveFile**
+> Object serveFile(aid, did, isThumb, isWeb)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final aid = aid_example; // String | 
+final did = did_example; // String | 
+final isThumb = true; // bool | 
+final isWeb = true; // bool | 
+
+try {
+    final result = api_instance.serveFile(aid, did, isThumb, isWeb);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->serveFile: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **aid** | **String**|  | 
+ **did** | **String**|  | 
+ **isThumb** | **bool**|  | [optional] 
+ **isWeb** | **bool**|  | [optional] 
+
+### Return type
+
+[**Object**](Object.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **uploadFile**
+> AssetFileUploadResponseDto uploadFile(assetData)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AssetApi();
+final assetData = BINARY_DATA_HERE; // MultipartFile | 
+
+try {
+    final result = api_instance.uploadFile(assetData);
+    print(result);
+} catch (e) {
+    print('Exception when calling AssetApi->uploadFile: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **assetData** | **MultipartFile**|  | 
+
+### Return type
+
+[**AssetFileUploadResponseDto**](AssetFileUploadResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: multipart/form-data
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
diff --git a/mobile/openapi/doc/AssetFileUploadResponseDto.md b/mobile/openapi/doc/AssetFileUploadResponseDto.md
new file mode 100644
index 0000000000..1b4f599e17
--- /dev/null
+++ b/mobile/openapi/doc/AssetFileUploadResponseDto.md
@@ -0,0 +1,15 @@
+# openapi.model.AssetFileUploadResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md
new file mode 100644
index 0000000000..e693549f6b
--- /dev/null
+++ b/mobile/openapi/doc/AssetResponseDto.md
@@ -0,0 +1,30 @@
+# openapi.model.AssetResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**type** | [**AssetTypeEnum**](AssetTypeEnum.md) |  | 
+**id** | **String** |  | 
+**deviceAssetId** | **String** |  | 
+**ownerId** | **String** |  | 
+**deviceId** | **String** |  | 
+**originalPath** | **String** |  | 
+**resizePath** | **String** |  | 
+**createdAt** | **String** |  | 
+**modifiedAt** | **String** |  | 
+**isFavorite** | **bool** |  | 
+**mimeType** | **String** |  | 
+**duration** | **String** |  | 
+**webpPath** | **String** |  | 
+**encodedVideoPath** | **String** |  | 
+**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) |  | [optional] 
+**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) |  | [optional] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/AssetTypeEnum.md b/mobile/openapi/doc/AssetTypeEnum.md
new file mode 100644
index 0000000000..8d514b090e
--- /dev/null
+++ b/mobile/openapi/doc/AssetTypeEnum.md
@@ -0,0 +1,14 @@
+# openapi.model.AssetTypeEnum
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/AuthenticationApi.md b/mobile/openapi/doc/AuthenticationApi.md
new file mode 100644
index 0000000000..46590ff5ad
--- /dev/null
+++ b/mobile/openapi/doc/AuthenticationApi.md
@@ -0,0 +1,141 @@
+# openapi.api.AuthenticationApi
+
+## Load the API package
+```dart
+import 'package:openapi/api.dart';
+```
+
+All URIs are relative to */api*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+[**adminSignUp**](AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up | 
+[**login**](AuthenticationApi.md#login) | **POST** /auth/login | 
+[**validateAccessToken**](AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | 
+
+
+# **adminSignUp**
+> AdminSignupResponseDto adminSignUp(signUpDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+
+final api_instance = AuthenticationApi();
+final signUpDto = SignUpDto(); // SignUpDto | 
+
+try {
+    final result = api_instance.adminSignUp(signUpDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AuthenticationApi->adminSignUp: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **signUpDto** | [**SignUpDto**](SignUpDto.md)|  | 
+
+### Return type
+
+[**AdminSignupResponseDto**](AdminSignupResponseDto.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **login**
+> LoginResponseDto login(loginCredentialDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+
+final api_instance = AuthenticationApi();
+final loginCredentialDto = LoginCredentialDto(); // LoginCredentialDto | 
+
+try {
+    final result = api_instance.login(loginCredentialDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling AuthenticationApi->login: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **loginCredentialDto** | [**LoginCredentialDto**](LoginCredentialDto.md)|  | 
+
+### Return type
+
+[**LoginResponseDto**](LoginResponseDto.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **validateAccessToken**
+> ValidateAccessTokenResponseDto validateAccessToken()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = AuthenticationApi();
+
+try {
+    final result = api_instance.validateAccessToken();
+    print(result);
+} catch (e) {
+    print('Exception when calling AuthenticationApi->validateAccessToken: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**ValidateAccessTokenResponseDto**](ValidateAccessTokenResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
diff --git a/mobile/openapi/doc/CheckDuplicateAssetDto.md b/mobile/openapi/doc/CheckDuplicateAssetDto.md
new file mode 100644
index 0000000000..259cdfc81c
--- /dev/null
+++ b/mobile/openapi/doc/CheckDuplicateAssetDto.md
@@ -0,0 +1,16 @@
+# openapi.model.CheckDuplicateAssetDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**deviceAssetId** | **String** |  | 
+**deviceId** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/CheckDuplicateAssetResponseDto.md b/mobile/openapi/doc/CheckDuplicateAssetResponseDto.md
new file mode 100644
index 0000000000..736d862243
--- /dev/null
+++ b/mobile/openapi/doc/CheckDuplicateAssetResponseDto.md
@@ -0,0 +1,15 @@
+# openapi.model.CheckDuplicateAssetResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**isExist** | **bool** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/CreateAlbumDto.md b/mobile/openapi/doc/CreateAlbumDto.md
new file mode 100644
index 0000000000..2f91dba103
--- /dev/null
+++ b/mobile/openapi/doc/CreateAlbumDto.md
@@ -0,0 +1,17 @@
+# openapi.model.CreateAlbumDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**albumName** | **String** |  | 
+**sharedWithUserIds** | **List<String>** |  | [optional] [default to const []]
+**assetIds** | **List<String>** |  | [optional] [default to const []]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/CreateDeviceInfoDto.md b/mobile/openapi/doc/CreateDeviceInfoDto.md
new file mode 100644
index 0000000000..d0629bb2d4
--- /dev/null
+++ b/mobile/openapi/doc/CreateDeviceInfoDto.md
@@ -0,0 +1,17 @@
+# openapi.model.CreateDeviceInfoDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**deviceType** | [**DeviceTypeEnum**](DeviceTypeEnum.md) |  | 
+**deviceId** | **String** |  | 
+**isAutoBackup** | **bool** |  | [optional] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/CreateProfileImageResponseDto.md b/mobile/openapi/doc/CreateProfileImageResponseDto.md
new file mode 100644
index 0000000000..3323e7df43
--- /dev/null
+++ b/mobile/openapi/doc/CreateProfileImageResponseDto.md
@@ -0,0 +1,16 @@
+# openapi.model.CreateProfileImageResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**userId** | **String** |  | 
+**profileImagePath** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/CreateUserDto.md b/mobile/openapi/doc/CreateUserDto.md
new file mode 100644
index 0000000000..c7b8b4ca28
--- /dev/null
+++ b/mobile/openapi/doc/CreateUserDto.md
@@ -0,0 +1,18 @@
+# openapi.model.CreateUserDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**email** | **String** |  | 
+**password** | **String** |  | 
+**firstName** | **String** |  | 
+**lastName** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/CuratedLocationsResponseDto.md b/mobile/openapi/doc/CuratedLocationsResponseDto.md
new file mode 100644
index 0000000000..abd86ea535
--- /dev/null
+++ b/mobile/openapi/doc/CuratedLocationsResponseDto.md
@@ -0,0 +1,19 @@
+# openapi.model.CuratedLocationsResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | 
+**city** | **String** |  | 
+**resizePath** | **String** |  | 
+**deviceAssetId** | **String** |  | 
+**deviceId** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/CuratedObjectsResponseDto.md b/mobile/openapi/doc/CuratedObjectsResponseDto.md
new file mode 100644
index 0000000000..559f25148f
--- /dev/null
+++ b/mobile/openapi/doc/CuratedObjectsResponseDto.md
@@ -0,0 +1,19 @@
+# openapi.model.CuratedObjectsResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | 
+**object** | **String** |  | 
+**resizePath** | **String** |  | 
+**deviceAssetId** | **String** |  | 
+**deviceId** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/DeleteAssetDto.md b/mobile/openapi/doc/DeleteAssetDto.md
new file mode 100644
index 0000000000..3b60d4eced
--- /dev/null
+++ b/mobile/openapi/doc/DeleteAssetDto.md
@@ -0,0 +1,15 @@
+# openapi.model.DeleteAssetDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**ids** | **List<String>** |  | [default to const []]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/DeleteAssetResponseDto.md b/mobile/openapi/doc/DeleteAssetResponseDto.md
new file mode 100644
index 0000000000..4cd44e030e
--- /dev/null
+++ b/mobile/openapi/doc/DeleteAssetResponseDto.md
@@ -0,0 +1,16 @@
+# openapi.model.DeleteAssetResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**status** | [**DeleteAssetStatus**](DeleteAssetStatus.md) |  | 
+**id** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/DeleteAssetStatus.md b/mobile/openapi/doc/DeleteAssetStatus.md
new file mode 100644
index 0000000000..4616d0ae06
--- /dev/null
+++ b/mobile/openapi/doc/DeleteAssetStatus.md
@@ -0,0 +1,14 @@
+# openapi.model.DeleteAssetStatus
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/DeviceInfoApi.md b/mobile/openapi/doc/DeviceInfoApi.md
new file mode 100644
index 0000000000..d1c032e65a
--- /dev/null
+++ b/mobile/openapi/doc/DeviceInfoApi.md
@@ -0,0 +1,109 @@
+# openapi.api.DeviceInfoApi
+
+## Load the API package
+```dart
+import 'package:openapi/api.dart';
+```
+
+All URIs are relative to */api*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+[**createDeviceInfo**](DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info | 
+[**updateDeviceInfo**](DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info | 
+
+
+# **createDeviceInfo**
+> DeviceInfoResponseDto createDeviceInfo(createDeviceInfoDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = DeviceInfoApi();
+final createDeviceInfoDto = CreateDeviceInfoDto(); // CreateDeviceInfoDto | 
+
+try {
+    final result = api_instance.createDeviceInfo(createDeviceInfoDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling DeviceInfoApi->createDeviceInfo: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **createDeviceInfoDto** | [**CreateDeviceInfoDto**](CreateDeviceInfoDto.md)|  | 
+
+### Return type
+
+[**DeviceInfoResponseDto**](DeviceInfoResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **updateDeviceInfo**
+> DeviceInfoResponseDto updateDeviceInfo(updateDeviceInfoDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = DeviceInfoApi();
+final updateDeviceInfoDto = UpdateDeviceInfoDto(); // UpdateDeviceInfoDto | 
+
+try {
+    final result = api_instance.updateDeviceInfo(updateDeviceInfoDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling DeviceInfoApi->updateDeviceInfo: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **updateDeviceInfoDto** | [**UpdateDeviceInfoDto**](UpdateDeviceInfoDto.md)|  | 
+
+### Return type
+
+[**DeviceInfoResponseDto**](DeviceInfoResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
diff --git a/mobile/openapi/doc/DeviceInfoResponseDto.md b/mobile/openapi/doc/DeviceInfoResponseDto.md
new file mode 100644
index 0000000000..95edd86ab7
--- /dev/null
+++ b/mobile/openapi/doc/DeviceInfoResponseDto.md
@@ -0,0 +1,20 @@
+# openapi.model.DeviceInfoResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **int** |  | 
+**deviceType** | [**DeviceTypeEnum**](DeviceTypeEnum.md) |  | 
+**userId** | **String** |  | 
+**deviceId** | **String** |  | 
+**createdAt** | **String** |  | 
+**isAutoBackup** | **bool** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/DeviceTypeEnum.md b/mobile/openapi/doc/DeviceTypeEnum.md
new file mode 100644
index 0000000000..ab8f99acbe
--- /dev/null
+++ b/mobile/openapi/doc/DeviceTypeEnum.md
@@ -0,0 +1,14 @@
+# openapi.model.DeviceTypeEnum
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/ExifResponseDto.md b/mobile/openapi/doc/ExifResponseDto.md
new file mode 100644
index 0000000000..0e96bdcbe9
--- /dev/null
+++ b/mobile/openapi/doc/ExifResponseDto.md
@@ -0,0 +1,34 @@
+# openapi.model.ExifResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | [optional] 
+**make** | **String** |  | [optional] 
+**model** | **String** |  | [optional] 
+**imageName** | **String** |  | [optional] 
+**exifImageWidth** | **num** |  | [optional] 
+**exifImageHeight** | **num** |  | [optional] 
+**fileSizeInByte** | **num** |  | [optional] 
+**orientation** | **String** |  | [optional] 
+**dateTimeOriginal** | [**DateTime**](DateTime.md) |  | [optional] 
+**modifyDate** | [**DateTime**](DateTime.md) |  | [optional] 
+**lensModel** | **String** |  | [optional] 
+**fNumber** | **num** |  | [optional] 
+**focalLength** | **num** |  | [optional] 
+**iso** | **num** |  | [optional] 
+**exposureTime** | **num** |  | [optional] 
+**latitude** | **num** |  | [optional] 
+**longitude** | **num** |  | [optional] 
+**city** | **String** |  | [optional] 
+**state** | **String** |  | [optional] 
+**country** | **String** |  | [optional] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/LoginCredentialDto.md b/mobile/openapi/doc/LoginCredentialDto.md
new file mode 100644
index 0000000000..bf8eb3d21e
--- /dev/null
+++ b/mobile/openapi/doc/LoginCredentialDto.md
@@ -0,0 +1,16 @@
+# openapi.model.LoginCredentialDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**email** | **String** |  | 
+**password** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/LoginResponseDto.md b/mobile/openapi/doc/LoginResponseDto.md
new file mode 100644
index 0000000000..b4033fe52c
--- /dev/null
+++ b/mobile/openapi/doc/LoginResponseDto.md
@@ -0,0 +1,22 @@
+# openapi.model.LoginResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**accessToken** | **String** |  | [readonly] 
+**userId** | **String** |  | [readonly] 
+**userEmail** | **String** |  | [readonly] 
+**firstName** | **String** |  | [readonly] 
+**lastName** | **String** |  | [readonly] 
+**profileImagePath** | **String** |  | [readonly] 
+**isAdmin** | **bool** |  | [readonly] 
+**shouldChangePassword** | **bool** |  | [readonly] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/RemoveAssetsDto.md b/mobile/openapi/doc/RemoveAssetsDto.md
new file mode 100644
index 0000000000..d2ab847324
--- /dev/null
+++ b/mobile/openapi/doc/RemoveAssetsDto.md
@@ -0,0 +1,15 @@
+# openapi.model.RemoveAssetsDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**assetIds** | **List<String>** |  | [default to const []]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/SearchAssetDto.md b/mobile/openapi/doc/SearchAssetDto.md
new file mode 100644
index 0000000000..1eadd2a0c1
--- /dev/null
+++ b/mobile/openapi/doc/SearchAssetDto.md
@@ -0,0 +1,15 @@
+# openapi.model.SearchAssetDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**searchTerm** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/ServerInfoApi.md b/mobile/openapi/doc/ServerInfoApi.md
new file mode 100644
index 0000000000..7d2840f8ca
--- /dev/null
+++ b/mobile/openapi/doc/ServerInfoApi.md
@@ -0,0 +1,127 @@
+# openapi.api.ServerInfoApi
+
+## Load the API package
+```dart
+import 'package:openapi/api.dart';
+```
+
+All URIs are relative to */api*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+[**getServerInfo**](ServerInfoApi.md#getserverinfo) | **GET** /server-info | 
+[**getServerVersion**](ServerInfoApi.md#getserverversion) | **GET** /server-info/version | 
+[**pingServer**](ServerInfoApi.md#pingserver) | **GET** /server-info/ping | 
+
+
+# **getServerInfo**
+> ServerInfoResponseDto getServerInfo()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+
+final api_instance = ServerInfoApi();
+
+try {
+    final result = api_instance.getServerInfo();
+    print(result);
+} catch (e) {
+    print('Exception when calling ServerInfoApi->getServerInfo: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**ServerInfoResponseDto**](ServerInfoResponseDto.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getServerVersion**
+> ServerVersionReponseDto getServerVersion()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+
+final api_instance = ServerInfoApi();
+
+try {
+    final result = api_instance.getServerVersion();
+    print(result);
+} catch (e) {
+    print('Exception when calling ServerInfoApi->getServerVersion: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**ServerVersionReponseDto**](ServerVersionReponseDto.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **pingServer**
+> ServerPingResponse pingServer()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+
+final api_instance = ServerInfoApi();
+
+try {
+    final result = api_instance.pingServer();
+    print(result);
+} catch (e) {
+    print('Exception when calling ServerInfoApi->pingServer: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**ServerPingResponse**](ServerPingResponse.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
diff --git a/mobile/openapi/doc/ServerInfoResponseDto.md b/mobile/openapi/doc/ServerInfoResponseDto.md
new file mode 100644
index 0000000000..75694d60de
--- /dev/null
+++ b/mobile/openapi/doc/ServerInfoResponseDto.md
@@ -0,0 +1,21 @@
+# openapi.model.ServerInfoResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**diskSizeRaw** | **int** |  | 
+**diskUseRaw** | **int** |  | 
+**diskAvailableRaw** | **int** |  | 
+**diskUsagePercentage** | **double** |  | 
+**diskSize** | **String** |  | 
+**diskUse** | **String** |  | 
+**diskAvailable** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/ServerPingResponse.md b/mobile/openapi/doc/ServerPingResponse.md
new file mode 100644
index 0000000000..97a79869fc
--- /dev/null
+++ b/mobile/openapi/doc/ServerPingResponse.md
@@ -0,0 +1,15 @@
+# openapi.model.ServerPingResponse
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**res** | **String** |  | [readonly] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/ServerVersionReponseDto.md b/mobile/openapi/doc/ServerVersionReponseDto.md
new file mode 100644
index 0000000000..e40920e506
--- /dev/null
+++ b/mobile/openapi/doc/ServerVersionReponseDto.md
@@ -0,0 +1,18 @@
+# openapi.model.ServerVersionReponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**major** | **int** |  | 
+**minor** | **int** |  | 
+**patch_** | **int** |  | 
+**build** | **int** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/SignUpDto.md b/mobile/openapi/doc/SignUpDto.md
new file mode 100644
index 0000000000..6ca6759a93
--- /dev/null
+++ b/mobile/openapi/doc/SignUpDto.md
@@ -0,0 +1,18 @@
+# openapi.model.SignUpDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**email** | **String** |  | 
+**password** | **String** |  | 
+**firstName** | **String** |  | 
+**lastName** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/SmartInfoResponseDto.md b/mobile/openapi/doc/SmartInfoResponseDto.md
new file mode 100644
index 0000000000..84a273768a
--- /dev/null
+++ b/mobile/openapi/doc/SmartInfoResponseDto.md
@@ -0,0 +1,17 @@
+# openapi.model.SmartInfoResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | [optional] 
+**tags** | **List<String>** |  | [optional] [default to const []]
+**objects** | **List<String>** |  | [optional] [default to const []]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/UpdateAlbumDto.md b/mobile/openapi/doc/UpdateAlbumDto.md
new file mode 100644
index 0000000000..e31c8b41e5
--- /dev/null
+++ b/mobile/openapi/doc/UpdateAlbumDto.md
@@ -0,0 +1,16 @@
+# openapi.model.UpdateAlbumDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**albumName** | **String** |  | 
+**ownerId** | **String** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/UpdateDeviceInfoDto.md b/mobile/openapi/doc/UpdateDeviceInfoDto.md
new file mode 100644
index 0000000000..75c98bf1b0
--- /dev/null
+++ b/mobile/openapi/doc/UpdateDeviceInfoDto.md
@@ -0,0 +1,17 @@
+# openapi.model.UpdateDeviceInfoDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**deviceType** | [**DeviceTypeEnum**](DeviceTypeEnum.md) |  | 
+**deviceId** | **String** |  | 
+**isAutoBackup** | **bool** |  | [optional] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/UpdateUserDto.md b/mobile/openapi/doc/UpdateUserDto.md
new file mode 100644
index 0000000000..1bdb496c5a
--- /dev/null
+++ b/mobile/openapi/doc/UpdateUserDto.md
@@ -0,0 +1,21 @@
+# openapi.model.UpdateUserDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | 
+**password** | **String** |  | [optional] 
+**firstName** | **String** |  | [optional] 
+**lastName** | **String** |  | [optional] 
+**isAdmin** | **bool** |  | [optional] 
+**shouldChangePassword** | **bool** |  | [optional] 
+**profileImagePath** | **String** |  | [optional] 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/UserApi.md b/mobile/openapi/doc/UserApi.md
new file mode 100644
index 0000000000..9a6aea5da8
--- /dev/null
+++ b/mobile/openapi/doc/UserApi.md
@@ -0,0 +1,329 @@
+# openapi.api.UserApi
+
+## Load the API package
+```dart
+import 'package:openapi/api.dart';
+```
+
+All URIs are relative to */api*
+
+Method | HTTP request | Description
+------------- | ------------- | -------------
+[**createProfileImage**](UserApi.md#createprofileimage) | **POST** /user/profile-image | 
+[**createUser**](UserApi.md#createuser) | **POST** /user | 
+[**getAllUsers**](UserApi.md#getallusers) | **GET** /user | 
+[**getMyUserInfo**](UserApi.md#getmyuserinfo) | **GET** /user/me | 
+[**getProfileImage**](UserApi.md#getprofileimage) | **GET** /user/profile-image/{userId} | 
+[**getUserCount**](UserApi.md#getusercount) | **GET** /user/count | 
+[**updateUser**](UserApi.md#updateuser) | **PUT** /user | 
+
+
+# **createProfileImage**
+> CreateProfileImageResponseDto createProfileImage(file)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = UserApi();
+final file = BINARY_DATA_HERE; // MultipartFile | 
+
+try {
+    final result = api_instance.createProfileImage(file);
+    print(result);
+} catch (e) {
+    print('Exception when calling UserApi->createProfileImage: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **file** | **MultipartFile**|  | 
+
+### Return type
+
+[**CreateProfileImageResponseDto**](CreateProfileImageResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: multipart/form-data
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **createUser**
+> UserResponseDto createUser(createUserDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = UserApi();
+final createUserDto = CreateUserDto(); // CreateUserDto | 
+
+try {
+    final result = api_instance.createUser(createUserDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling UserApi->createUser: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **createUserDto** | [**CreateUserDto**](CreateUserDto.md)|  | 
+
+### Return type
+
+[**UserResponseDto**](UserResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getAllUsers**
+> List<UserResponseDto> getAllUsers(isAll)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = UserApi();
+final isAll = true; // bool | 
+
+try {
+    final result = api_instance.getAllUsers(isAll);
+    print(result);
+} catch (e) {
+    print('Exception when calling UserApi->getAllUsers: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **isAll** | **bool**|  | 
+
+### Return type
+
+[**List<UserResponseDto>**](UserResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getMyUserInfo**
+> UserResponseDto getMyUserInfo()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = UserApi();
+
+try {
+    final result = api_instance.getMyUserInfo();
+    print(result);
+} catch (e) {
+    print('Exception when calling UserApi->getMyUserInfo: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**UserResponseDto**](UserResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getProfileImage**
+> Object getProfileImage(userId)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+
+final api_instance = UserApi();
+final userId = userId_example; // String | 
+
+try {
+    final result = api_instance.getProfileImage(userId);
+    print(result);
+} catch (e) {
+    print('Exception when calling UserApi->getProfileImage: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **userId** | **String**|  | 
+
+### Return type
+
+[**Object**](Object.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **getUserCount**
+> UserCountResponseDto getUserCount()
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+
+final api_instance = UserApi();
+
+try {
+    final result = api_instance.getUserCount();
+    print(result);
+} catch (e) {
+    print('Exception when calling UserApi->getUserCount: $e\n');
+}
+```
+
+### Parameters
+This endpoint does not need any parameter.
+
+### Return type
+
+[**UserCountResponseDto**](UserCountResponseDto.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
+# **updateUser**
+> UserResponseDto updateUser(updateUserDto)
+
+
+
+### Example
+```dart
+import 'package:openapi/api.dart';
+// TODO Configure HTTP Bearer authorization: bearer
+// Case 1. Use String Token
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
+// Case 2. Use Function which generate token.
+// String yourTokenGeneratorFunction() { ... }
+//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
+
+final api_instance = UserApi();
+final updateUserDto = UpdateUserDto(); // UpdateUserDto | 
+
+try {
+    final result = api_instance.updateUser(updateUserDto);
+    print(result);
+} catch (e) {
+    print('Exception when calling UserApi->updateUser: $e\n');
+}
+```
+
+### Parameters
+
+Name | Type | Description  | Notes
+------------- | ------------- | ------------- | -------------
+ **updateUserDto** | [**UpdateUserDto**](UpdateUserDto.md)|  | 
+
+### Return type
+
+[**UserResponseDto**](UserResponseDto.md)
+
+### Authorization
+
+[bearer](../README.md#bearer)
+
+### HTTP request headers
+
+ - **Content-Type**: application/json
+ - **Accept**: application/json
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
diff --git a/mobile/openapi/doc/UserCountResponseDto.md b/mobile/openapi/doc/UserCountResponseDto.md
new file mode 100644
index 0000000000..65dab56866
--- /dev/null
+++ b/mobile/openapi/doc/UserCountResponseDto.md
@@ -0,0 +1,15 @@
+# openapi.model.UserCountResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**userCount** | **int** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/UserResponseDto.md b/mobile/openapi/doc/UserResponseDto.md
new file mode 100644
index 0000000000..d56b444ee0
--- /dev/null
+++ b/mobile/openapi/doc/UserResponseDto.md
@@ -0,0 +1,22 @@
+# openapi.model.UserResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **String** |  | 
+**email** | **String** |  | 
+**firstName** | **String** |  | 
+**lastName** | **String** |  | 
+**createdAt** | **String** |  | 
+**profileImagePath** | **String** |  | 
+**shouldChangePassword** | **bool** |  | 
+**isAdmin** | **bool** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/doc/ValidateAccessTokenResponseDto.md b/mobile/openapi/doc/ValidateAccessTokenResponseDto.md
new file mode 100644
index 0000000000..60e1e02cbf
--- /dev/null
+++ b/mobile/openapi/doc/ValidateAccessTokenResponseDto.md
@@ -0,0 +1,15 @@
+# openapi.model.ValidateAccessTokenResponseDto
+
+## Load the model package
+```dart
+import 'package:openapi/api.dart';
+```
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**authStatus** | **bool** |  | 
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/mobile/openapi/git_push.sh b/mobile/openapi/git_push.sh
new file mode 100644
index 0000000000..f53a75d4fa
--- /dev/null
+++ b/mobile/openapi/git_push.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+    git_host="github.com"
+    echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+    git_user_id="GIT_USER_ID"
+    echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+    git_repo_id="GIT_REPO_ID"
+    echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+    release_note="Minor update"
+    echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=$(git remote)
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+    if [ "$GIT_TOKEN" = "" ]; then
+        echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+        git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+    else
+        git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
+    fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'
diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart
new file mode 100644
index 0000000000..9d632532f1
--- /dev/null
+++ b/mobile/openapi/lib/api.dart
@@ -0,0 +1,82 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+library openapi.api;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:http/http.dart';
+import 'package:intl/intl.dart';
+import 'package:meta/meta.dart';
+
+part 'api_client.dart';
+part 'api_helper.dart';
+part 'api_exception.dart';
+part 'auth/authentication.dart';
+part 'auth/api_key_auth.dart';
+part 'auth/oauth.dart';
+part 'auth/http_basic_auth.dart';
+part 'auth/http_bearer_auth.dart';
+
+part 'api/album_api.dart';
+part 'api/asset_api.dart';
+part 'api/authentication_api.dart';
+part 'api/device_info_api.dart';
+part 'api/server_info_api.dart';
+part 'api/user_api.dart';
+
+part 'model/add_assets_dto.dart';
+part 'model/add_users_dto.dart';
+part 'model/admin_signup_response_dto.dart';
+part 'model/album_response_dto.dart';
+part 'model/asset_file_upload_response_dto.dart';
+part 'model/asset_response_dto.dart';
+part 'model/asset_type_enum.dart';
+part 'model/check_duplicate_asset_dto.dart';
+part 'model/check_duplicate_asset_response_dto.dart';
+part 'model/create_album_dto.dart';
+part 'model/create_device_info_dto.dart';
+part 'model/create_profile_image_response_dto.dart';
+part 'model/create_user_dto.dart';
+part 'model/curated_locations_response_dto.dart';
+part 'model/curated_objects_response_dto.dart';
+part 'model/delete_asset_dto.dart';
+part 'model/delete_asset_response_dto.dart';
+part 'model/delete_asset_status.dart';
+part 'model/device_info_response_dto.dart';
+part 'model/device_type_enum.dart';
+part 'model/exif_response_dto.dart';
+part 'model/login_credential_dto.dart';
+part 'model/login_response_dto.dart';
+part 'model/remove_assets_dto.dart';
+part 'model/search_asset_dto.dart';
+part 'model/server_info_response_dto.dart';
+part 'model/server_ping_response.dart';
+part 'model/server_version_reponse_dto.dart';
+part 'model/sign_up_dto.dart';
+part 'model/smart_info_response_dto.dart';
+part 'model/update_album_dto.dart';
+part 'model/update_device_info_dto.dart';
+part 'model/update_user_dto.dart';
+part 'model/user_count_response_dto.dart';
+part 'model/user_response_dto.dart';
+part 'model/validate_access_token_response_dto.dart';
+
+
+const _delimiters = {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'};
+const _dateEpochMarker = 'epoch';
+final _dateFormatter = DateFormat('yyyy-MM-dd');
+final _regList = RegExp(r'^List<(.*)>$');
+final _regSet = RegExp(r'^Set<(.*)>$');
+final _regMap = RegExp(r'^Map<String,(.*)>$');
+
+ApiClient defaultApiClient = ApiClient();
diff --git a/mobile/openapi/lib/api/album_api.dart b/mobile/openapi/lib/api/album_api.dart
new file mode 100644
index 0000000000..b0d7f5b8ca
--- /dev/null
+++ b/mobile/openapi/lib/api/album_api.dart
@@ -0,0 +1,452 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class AlbumApi {
+  AlbumApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
+
+  final ApiClient apiClient;
+
+  /// Performs an HTTP 'PUT /album/{albumId}/assets' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [AddAssetsDto] addAssetsDto (required):
+  Future<Response> addAssetsToAlbumWithHttpInfo(String albumId, AddAssetsDto addAssetsDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album/{albumId}/assets'
+      .replaceAll('{albumId}', albumId);
+
+    // ignore: prefer_final_locals
+    Object? postBody = addAssetsDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'PUT',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [AddAssetsDto] addAssetsDto (required):
+  Future<AlbumResponseDto?> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto,) async {
+    final response = await addAssetsToAlbumWithHttpInfo(albumId, addAssetsDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'PUT /album/{albumId}/users' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [AddUsersDto] addUsersDto (required):
+  Future<Response> addUsersToAlbumWithHttpInfo(String albumId, AddUsersDto addUsersDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album/{albumId}/users'
+      .replaceAll('{albumId}', albumId);
+
+    // ignore: prefer_final_locals
+    Object? postBody = addUsersDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'PUT',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [AddUsersDto] addUsersDto (required):
+  Future<AlbumResponseDto?> addUsersToAlbum(String albumId, AddUsersDto addUsersDto,) async {
+    final response = await addUsersToAlbumWithHttpInfo(albumId, addUsersDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'POST /album' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [CreateAlbumDto] createAlbumDto (required):
+  Future<Response> createAlbumWithHttpInfo(CreateAlbumDto createAlbumDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album';
+
+    // ignore: prefer_final_locals
+    Object? postBody = createAlbumDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [CreateAlbumDto] createAlbumDto (required):
+  Future<AlbumResponseDto?> createAlbum(CreateAlbumDto createAlbumDto,) async {
+    final response = await createAlbumWithHttpInfo(createAlbumDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'DELETE /album/{albumId}' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  Future<Response> deleteAlbumWithHttpInfo(String albumId,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album/{albumId}'
+      .replaceAll('{albumId}', albumId);
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'DELETE',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  Future<void> deleteAlbum(String albumId,) async {
+    final response = await deleteAlbumWithHttpInfo(albumId,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+  }
+
+  /// Performs an HTTP 'GET /album/{albumId}' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  Future<Response> getAlbumInfoWithHttpInfo(String albumId,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album/{albumId}'
+      .replaceAll('{albumId}', albumId);
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  Future<AlbumResponseDto?> getAlbumInfo(String albumId,) async {
+    final response = await getAlbumInfoWithHttpInfo(albumId,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /album' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [bool] shared:
+  Future<Response> getAllAlbumsWithHttpInfo({ bool? shared, }) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    if (shared != null) {
+      queryParams.addAll(_queryParams('', 'shared', shared));
+    }
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [bool] shared:
+  Future<List<AlbumResponseDto>?> getAllAlbums({ bool? shared, }) async {
+    final response = await getAllAlbumsWithHttpInfo( shared: shared, );
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<AlbumResponseDto>') as List)
+        .cast<AlbumResponseDto>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'DELETE /album/{albumId}/assets' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [RemoveAssetsDto] removeAssetsDto (required):
+  Future<Response> removeAssetFromAlbumWithHttpInfo(String albumId, RemoveAssetsDto removeAssetsDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album/{albumId}/assets'
+      .replaceAll('{albumId}', albumId);
+
+    // ignore: prefer_final_locals
+    Object? postBody = removeAssetsDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'DELETE',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [RemoveAssetsDto] removeAssetsDto (required):
+  Future<void> removeAssetFromAlbum(String albumId, RemoveAssetsDto removeAssetsDto,) async {
+    final response = await removeAssetFromAlbumWithHttpInfo(albumId, removeAssetsDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+  }
+
+  /// Performs an HTTP 'DELETE /album/{albumId}/user/{userId}' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [String] userId (required):
+  Future<Response> removeUserFromAlbumWithHttpInfo(String albumId, String userId,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album/{albumId}/user/{userId}'
+      .replaceAll('{albumId}', albumId)
+      .replaceAll('{userId}', userId);
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'DELETE',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [String] userId (required):
+  Future<void> removeUserFromAlbum(String albumId, String userId,) async {
+    final response = await removeUserFromAlbumWithHttpInfo(albumId, userId,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+  }
+
+  /// Performs an HTTP 'PATCH /album/{albumId}' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [UpdateAlbumDto] updateAlbumDto (required):
+  Future<Response> updateAlbumInfoWithHttpInfo(String albumId, UpdateAlbumDto updateAlbumDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/album/{albumId}'
+      .replaceAll('{albumId}', albumId);
+
+    // ignore: prefer_final_locals
+    Object? postBody = updateAlbumDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'PATCH',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] albumId (required):
+  ///
+  /// * [UpdateAlbumDto] updateAlbumDto (required):
+  Future<AlbumResponseDto?> updateAlbumInfo(String albumId, UpdateAlbumDto updateAlbumDto,) async {
+    final response = await updateAlbumInfoWithHttpInfo(albumId, updateAlbumDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto;
+    
+    }
+    return null;
+  }
+}
diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart
new file mode 100644
index 0000000000..7d541c69f7
--- /dev/null
+++ b/mobile/openapi/lib/api/asset_api.dart
@@ -0,0 +1,715 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class AssetApi {
+  AssetApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
+
+  final ApiClient apiClient;
+
+  /// 
+  ///
+  /// Check duplicated asset before uploading - for Web upload used
+  ///
+  /// Note: This method returns the HTTP [Response].
+  ///
+  /// Parameters:
+  ///
+  /// * [CheckDuplicateAssetDto] checkDuplicateAssetDto (required):
+  Future<Response> checkDuplicateAssetWithHttpInfo(CheckDuplicateAssetDto checkDuplicateAssetDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/check';
+
+    // ignore: prefer_final_locals
+    Object? postBody = checkDuplicateAssetDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// 
+  ///
+  /// Check duplicated asset before uploading - for Web upload used
+  ///
+  /// Parameters:
+  ///
+  /// * [CheckDuplicateAssetDto] checkDuplicateAssetDto (required):
+  Future<CheckDuplicateAssetResponseDto?> checkDuplicateAsset(CheckDuplicateAssetDto checkDuplicateAssetDto,) async {
+    final response = await checkDuplicateAssetWithHttpInfo(checkDuplicateAssetDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'CheckDuplicateAssetResponseDto',) as CheckDuplicateAssetResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'DELETE /asset' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [DeleteAssetDto] deleteAssetDto (required):
+  Future<Response> deleteAssetWithHttpInfo(DeleteAssetDto deleteAssetDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset';
+
+    // ignore: prefer_final_locals
+    Object? postBody = deleteAssetDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'DELETE',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [DeleteAssetDto] deleteAssetDto (required):
+  Future<List<DeleteAssetResponseDto>?> deleteAsset(DeleteAssetDto deleteAssetDto,) async {
+    final response = await deleteAssetWithHttpInfo(deleteAssetDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<DeleteAssetResponseDto>') as List)
+        .cast<DeleteAssetResponseDto>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /asset/download' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] aid (required):
+  ///
+  /// * [String] did (required):
+  ///
+  /// * [bool] isThumb:
+  ///
+  /// * [bool] isWeb:
+  Future<Response> downloadFileWithHttpInfo(String aid, String did, { bool? isThumb, bool? isWeb, }) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/download';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+      queryParams.addAll(_queryParams('', 'aid', aid));
+      queryParams.addAll(_queryParams('', 'did', did));
+    if (isThumb != null) {
+      queryParams.addAll(_queryParams('', 'isThumb', isThumb));
+    }
+    if (isWeb != null) {
+      queryParams.addAll(_queryParams('', 'isWeb', isWeb));
+    }
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] aid (required):
+  ///
+  /// * [String] did (required):
+  ///
+  /// * [bool] isThumb:
+  ///
+  /// * [bool] isWeb:
+  Future<Object?> downloadFile(String aid, String did, { bool? isThumb, bool? isWeb, }) async {
+    final response = await downloadFileWithHttpInfo(aid, did,  isThumb: isThumb, isWeb: isWeb, );
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object;
+    
+    }
+    return null;
+  }
+
+  /// 
+  ///
+  /// Get all AssetEntity belong to the user
+  ///
+  /// Note: This method returns the HTTP [Response].
+  Future<Response> getAllAssetsWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// 
+  ///
+  /// Get all AssetEntity belong to the user
+  Future<List<AssetResponseDto>?> getAllAssets() async {
+    final response = await getAllAssetsWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
+        .cast<AssetResponseDto>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// 
+  ///
+  /// Get a single asset's information
+  ///
+  /// Note: This method returns the HTTP [Response].
+  ///
+  /// Parameters:
+  ///
+  /// * [String] assetId (required):
+  Future<Response> getAssetByIdWithHttpInfo(String assetId,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/assetById/{assetId}'
+      .replaceAll('{assetId}', assetId);
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// 
+  ///
+  /// Get a single asset's information
+  ///
+  /// Parameters:
+  ///
+  /// * [String] assetId (required):
+  Future<AssetResponseDto?> getAssetById(String assetId,) async {
+    final response = await getAssetByIdWithHttpInfo(assetId,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetResponseDto',) as AssetResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /asset/searchTerm' operation and returns the [Response].
+  Future<Response> getAssetSearchTermsWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/searchTerm';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<List<String>?> getAssetSearchTerms() async {
+    final response = await getAssetSearchTermsWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<String>') as List)
+        .cast<String>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /asset/thumbnail/{assetId}' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] assetId (required):
+  Future<Response> getAssetThumbnailWithHttpInfo(String assetId,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/thumbnail/{assetId}'
+      .replaceAll('{assetId}', assetId);
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] assetId (required):
+  Future<Object?> getAssetThumbnail(String assetId,) async {
+    final response = await getAssetThumbnailWithHttpInfo(assetId,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /asset/allLocation' operation and returns the [Response].
+  Future<Response> getCuratedLocationsWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/allLocation';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<List<CuratedLocationsResponseDto>?> getCuratedLocations() async {
+    final response = await getCuratedLocationsWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<CuratedLocationsResponseDto>') as List)
+        .cast<CuratedLocationsResponseDto>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /asset/allObjects' operation and returns the [Response].
+  Future<Response> getCuratedObjectsWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/allObjects';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<List<CuratedObjectsResponseDto>?> getCuratedObjects() async {
+    final response = await getCuratedObjectsWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<CuratedObjectsResponseDto>') as List)
+        .cast<CuratedObjectsResponseDto>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// 
+  ///
+  /// Get all asset of a device that are in the database, ID only.
+  ///
+  /// Note: This method returns the HTTP [Response].
+  ///
+  /// Parameters:
+  ///
+  /// * [String] deviceId (required):
+  Future<Response> getUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/{deviceId}'
+      .replaceAll('{deviceId}', deviceId);
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// 
+  ///
+  /// Get all asset of a device that are in the database, ID only.
+  ///
+  /// Parameters:
+  ///
+  /// * [String] deviceId (required):
+  Future<List<String>?> getUserAssetsByDeviceId(String deviceId,) async {
+    final response = await getUserAssetsByDeviceIdWithHttpInfo(deviceId,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<String>') as List)
+        .cast<String>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'POST /asset/search' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [SearchAssetDto] searchAssetDto (required):
+  Future<Response> searchAssetWithHttpInfo(SearchAssetDto searchAssetDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/search';
+
+    // ignore: prefer_final_locals
+    Object? postBody = searchAssetDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [SearchAssetDto] searchAssetDto (required):
+  Future<List<AssetResponseDto>?> searchAsset(SearchAssetDto searchAssetDto,) async {
+    final response = await searchAssetWithHttpInfo(searchAssetDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
+        .cast<AssetResponseDto>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /asset/file' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] aid (required):
+  ///
+  /// * [String] did (required):
+  ///
+  /// * [bool] isThumb:
+  ///
+  /// * [bool] isWeb:
+  Future<Response> serveFileWithHttpInfo(String aid, String did, { bool? isThumb, bool? isWeb, }) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/file';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+      queryParams.addAll(_queryParams('', 'aid', aid));
+      queryParams.addAll(_queryParams('', 'did', did));
+    if (isThumb != null) {
+      queryParams.addAll(_queryParams('', 'isThumb', isThumb));
+    }
+    if (isWeb != null) {
+      queryParams.addAll(_queryParams('', 'isWeb', isWeb));
+    }
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] aid (required):
+  ///
+  /// * [String] did (required):
+  ///
+  /// * [bool] isThumb:
+  ///
+  /// * [bool] isWeb:
+  Future<Object?> serveFile(String aid, String did, { bool? isThumb, bool? isWeb, }) async {
+    final response = await serveFileWithHttpInfo(aid, did,  isThumb: isThumb, isWeb: isWeb, );
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'POST /asset/upload' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [MultipartFile] assetData (required):
+  Future<Response> uploadFileWithHttpInfo(MultipartFile assetData,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/asset/upload';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['multipart/form-data'];
+
+    bool hasFields = false;
+    final mp = MultipartRequest('POST', Uri.parse(path));
+    if (assetData != null) {
+      hasFields = true;
+      mp.fields[r'assetData'] = assetData.field;
+      mp.files.add(assetData);
+    }
+    if (hasFields) {
+      postBody = mp;
+    }
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [MultipartFile] assetData (required):
+  Future<AssetFileUploadResponseDto?> uploadFile(MultipartFile assetData,) async {
+    final response = await uploadFileWithHttpInfo(assetData,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetFileUploadResponseDto',) as AssetFileUploadResponseDto;
+    
+    }
+    return null;
+  }
+}
diff --git a/mobile/openapi/lib/api/authentication_api.dart b/mobile/openapi/lib/api/authentication_api.dart
new file mode 100644
index 0000000000..5fda658eae
--- /dev/null
+++ b/mobile/openapi/lib/api/authentication_api.dart
@@ -0,0 +1,153 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class AuthenticationApi {
+  AuthenticationApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
+
+  final ApiClient apiClient;
+
+  /// Performs an HTTP 'POST /auth/admin-sign-up' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [SignUpDto] signUpDto (required):
+  Future<Response> adminSignUpWithHttpInfo(SignUpDto signUpDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/auth/admin-sign-up';
+
+    // ignore: prefer_final_locals
+    Object? postBody = signUpDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [SignUpDto] signUpDto (required):
+  Future<AdminSignupResponseDto?> adminSignUp(SignUpDto signUpDto,) async {
+    final response = await adminSignUpWithHttpInfo(signUpDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AdminSignupResponseDto',) as AdminSignupResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'POST /auth/login' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [LoginCredentialDto] loginCredentialDto (required):
+  Future<Response> loginWithHttpInfo(LoginCredentialDto loginCredentialDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/auth/login';
+
+    // ignore: prefer_final_locals
+    Object? postBody = loginCredentialDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [LoginCredentialDto] loginCredentialDto (required):
+  Future<LoginResponseDto?> login(LoginCredentialDto loginCredentialDto,) async {
+    final response = await loginWithHttpInfo(loginCredentialDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LoginResponseDto',) as LoginResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'POST /auth/validateToken' operation and returns the [Response].
+  Future<Response> validateAccessTokenWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/auth/validateToken';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<ValidateAccessTokenResponseDto?> validateAccessToken() async {
+    final response = await validateAccessTokenWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ValidateAccessTokenResponseDto',) as ValidateAccessTokenResponseDto;
+    
+    }
+    return null;
+  }
+}
diff --git a/mobile/openapi/lib/api/device_info_api.dart b/mobile/openapi/lib/api/device_info_api.dart
new file mode 100644
index 0000000000..c365ac60d2
--- /dev/null
+++ b/mobile/openapi/lib/api/device_info_api.dart
@@ -0,0 +1,112 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class DeviceInfoApi {
+  DeviceInfoApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
+
+  final ApiClient apiClient;
+
+  /// Performs an HTTP 'POST /device-info' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [CreateDeviceInfoDto] createDeviceInfoDto (required):
+  Future<Response> createDeviceInfoWithHttpInfo(CreateDeviceInfoDto createDeviceInfoDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/device-info';
+
+    // ignore: prefer_final_locals
+    Object? postBody = createDeviceInfoDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [CreateDeviceInfoDto] createDeviceInfoDto (required):
+  Future<DeviceInfoResponseDto?> createDeviceInfo(CreateDeviceInfoDto createDeviceInfoDto,) async {
+    final response = await createDeviceInfoWithHttpInfo(createDeviceInfoDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'DeviceInfoResponseDto',) as DeviceInfoResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'PATCH /device-info' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [UpdateDeviceInfoDto] updateDeviceInfoDto (required):
+  Future<Response> updateDeviceInfoWithHttpInfo(UpdateDeviceInfoDto updateDeviceInfoDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/device-info';
+
+    // ignore: prefer_final_locals
+    Object? postBody = updateDeviceInfoDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'PATCH',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [UpdateDeviceInfoDto] updateDeviceInfoDto (required):
+  Future<DeviceInfoResponseDto?> updateDeviceInfo(UpdateDeviceInfoDto updateDeviceInfoDto,) async {
+    final response = await updateDeviceInfoWithHttpInfo(updateDeviceInfoDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'DeviceInfoResponseDto',) as DeviceInfoResponseDto;
+    
+    }
+    return null;
+  }
+}
diff --git a/mobile/openapi/lib/api/server_info_api.dart b/mobile/openapi/lib/api/server_info_api.dart
new file mode 100644
index 0000000000..8af914b1ce
--- /dev/null
+++ b/mobile/openapi/lib/api/server_info_api.dart
@@ -0,0 +1,141 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class ServerInfoApi {
+  ServerInfoApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
+
+  final ApiClient apiClient;
+
+  /// Performs an HTTP 'GET /server-info' operation and returns the [Response].
+  Future<Response> getServerInfoWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/server-info';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<ServerInfoResponseDto?> getServerInfo() async {
+    final response = await getServerInfoWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerInfoResponseDto',) as ServerInfoResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /server-info/version' operation and returns the [Response].
+  Future<Response> getServerVersionWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/server-info/version';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<ServerVersionReponseDto?> getServerVersion() async {
+    final response = await getServerVersionWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerVersionReponseDto',) as ServerVersionReponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /server-info/ping' operation and returns the [Response].
+  Future<Response> pingServerWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/server-info/ping';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<ServerPingResponse?> pingServer() async {
+    final response = await pingServerWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerPingResponse',) as ServerPingResponse;
+    
+    }
+    return null;
+  }
+}
diff --git a/mobile/openapi/lib/api/user_api.dart b/mobile/openapi/lib/api/user_api.dart
new file mode 100644
index 0000000000..69bd819de8
--- /dev/null
+++ b/mobile/openapi/lib/api/user_api.dart
@@ -0,0 +1,351 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class UserApi {
+  UserApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
+
+  final ApiClient apiClient;
+
+  /// Performs an HTTP 'POST /user/profile-image' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [MultipartFile] file (required):
+  Future<Response> createProfileImageWithHttpInfo(MultipartFile file,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/user/profile-image';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['multipart/form-data'];
+
+    bool hasFields = false;
+    final mp = MultipartRequest('POST', Uri.parse(path));
+    if (file != null) {
+      hasFields = true;
+      mp.fields[r'file'] = file.field;
+      mp.files.add(file);
+    }
+    if (hasFields) {
+      postBody = mp;
+    }
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [MultipartFile] file (required):
+  Future<CreateProfileImageResponseDto?> createProfileImage(MultipartFile file,) async {
+    final response = await createProfileImageWithHttpInfo(file,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'CreateProfileImageResponseDto',) as CreateProfileImageResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'POST /user' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [CreateUserDto] createUserDto (required):
+  Future<Response> createUserWithHttpInfo(CreateUserDto createUserDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/user';
+
+    // ignore: prefer_final_locals
+    Object? postBody = createUserDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'POST',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [CreateUserDto] createUserDto (required):
+  Future<UserResponseDto?> createUser(CreateUserDto createUserDto,) async {
+    final response = await createUserWithHttpInfo(createUserDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /user' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [bool] isAll (required):
+  Future<Response> getAllUsersWithHttpInfo(bool isAll,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/user';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+      queryParams.addAll(_queryParams('', 'isAll', isAll));
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [bool] isAll (required):
+  Future<List<UserResponseDto>?> getAllUsers(bool isAll,) async {
+    final response = await getAllUsersWithHttpInfo(isAll,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      final responseBody = await _decodeBodyBytes(response);
+      return (await apiClient.deserializeAsync(responseBody, 'List<UserResponseDto>') as List)
+        .cast<UserResponseDto>()
+        .toList();
+
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /user/me' operation and returns the [Response].
+  Future<Response> getMyUserInfoWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/user/me';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<UserResponseDto?> getMyUserInfo() async {
+    final response = await getMyUserInfoWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /user/profile-image/{userId}' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [String] userId (required):
+  Future<Response> getProfileImageWithHttpInfo(String userId,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/user/profile-image/{userId}'
+      .replaceAll('{userId}', userId);
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [String] userId (required):
+  Future<Object?> getProfileImage(String userId,) async {
+    final response = await getProfileImageWithHttpInfo(userId,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'Object',) as Object;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'GET /user/count' operation and returns the [Response].
+  Future<Response> getUserCountWithHttpInfo() async {
+    // ignore: prefer_const_declarations
+    final path = r'/user/count';
+
+    // ignore: prefer_final_locals
+    Object? postBody;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>[];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'GET',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  Future<UserCountResponseDto?> getUserCount() async {
+    final response = await getUserCountWithHttpInfo();
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserCountResponseDto',) as UserCountResponseDto;
+    
+    }
+    return null;
+  }
+
+  /// Performs an HTTP 'PUT /user' operation and returns the [Response].
+  /// Parameters:
+  ///
+  /// * [UpdateUserDto] updateUserDto (required):
+  Future<Response> updateUserWithHttpInfo(UpdateUserDto updateUserDto,) async {
+    // ignore: prefer_const_declarations
+    final path = r'/user';
+
+    // ignore: prefer_final_locals
+    Object? postBody = updateUserDto;
+
+    final queryParams = <QueryParam>[];
+    final headerParams = <String, String>{};
+    final formParams = <String, String>{};
+
+    const contentTypes = <String>['application/json'];
+
+
+    return apiClient.invokeAPI(
+      path,
+      'PUT',
+      queryParams,
+      postBody,
+      headerParams,
+      formParams,
+      contentTypes.isEmpty ? null : contentTypes.first,
+    );
+  }
+
+  /// Parameters:
+  ///
+  /// * [UpdateUserDto] updateUserDto (required):
+  Future<UserResponseDto?> updateUser(UpdateUserDto updateUserDto,) async {
+    final response = await updateUserWithHttpInfo(updateUserDto,);
+    if (response.statusCode >= HttpStatus.badRequest) {
+      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
+    }
+    // When a remote server returns no body with a status of 204, we shall not decode it.
+    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
+    // FormatException when trying to decode an empty string.
+    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
+      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto;
+    
+    }
+    return null;
+  }
+}
diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart
new file mode 100644
index 0000000000..ae695c9698
--- /dev/null
+++ b/mobile/openapi/lib/api_client.dart
@@ -0,0 +1,327 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ApiClient {
+  ApiClient({this.basePath = '/api', this.authentication});
+
+  final String basePath;
+
+  var _client = Client();
+
+  /// Returns the current HTTP [Client] instance to use in this class.
+  ///
+  /// The return value is guaranteed to never be null.
+  Client get client => _client;
+
+  /// Requests to use a new HTTP [Client] in this class.
+  set client(Client newClient) {
+    _client = newClient;
+  }
+
+  final _defaultHeaderMap = <String, String>{};
+  final Authentication? authentication;
+
+  void addDefaultHeader(String key, String value) {
+     _defaultHeaderMap[key] = value;
+  }
+
+  Map<String,String> get defaultHeaderMap => _defaultHeaderMap;
+
+  // We don't use a Map<String, String> for queryParams.
+  // If collectionFormat is 'multi', a key might appear multiple times.
+  Future<Response> invokeAPI(
+    String path,
+    String method,
+    List<QueryParam> queryParams,
+    Object? body,
+    Map<String, String> headerParams,
+    Map<String, String> formParams,
+    String? contentType,
+  ) async {
+    _updateParamsForAuth(queryParams, headerParams);
+
+    headerParams.addAll(_defaultHeaderMap);
+    if (contentType != null) {
+      headerParams['Content-Type'] = contentType;
+    }
+
+    final urlEncodedQueryParams = queryParams.map((param) => '$param');
+    final queryString = urlEncodedQueryParams.isNotEmpty ? '?${urlEncodedQueryParams.join('&')}' : '';
+    final uri = Uri.parse('$basePath$path$queryString');
+
+    try {
+      // Special case for uploading a single file which isn't a 'multipart/form-data'.
+      if (
+        body is MultipartFile && (contentType == null ||
+        !contentType.toLowerCase().startsWith('multipart/form-data'))
+      ) {
+        final request = StreamedRequest(method, uri);
+        request.headers.addAll(headerParams);
+        request.contentLength = body.length;
+        body.finalize().listen(
+          request.sink.add,
+          onDone: request.sink.close,
+          // ignore: avoid_types_on_closure_parameters
+          onError: (Object error, StackTrace trace) => request.sink.close(),
+          cancelOnError: true,
+        );
+        final response = await _client.send(request);
+        return Response.fromStream(response);
+      }
+
+      if (body is MultipartRequest) {
+        final request = MultipartRequest(method, uri);
+        request.fields.addAll(body.fields);
+        request.files.addAll(body.files);
+        request.headers.addAll(body.headers);
+        request.headers.addAll(headerParams);
+        final response = await _client.send(request);
+        return Response.fromStream(response);
+      }
+
+      final msgBody = contentType == 'application/x-www-form-urlencoded'
+        ? formParams
+        : await serializeAsync(body);
+      final nullableHeaderParams = headerParams.isEmpty ? null : headerParams;
+
+      switch(method) {
+        case 'POST': return await _client.post(uri, headers: nullableHeaderParams, body: msgBody,);
+        case 'PUT': return await _client.put(uri, headers: nullableHeaderParams, body: msgBody,);
+        case 'DELETE': return await _client.delete(uri, headers: nullableHeaderParams, body: msgBody,);
+        case 'PATCH': return await _client.patch(uri, headers: nullableHeaderParams, body: msgBody,);
+        case 'HEAD': return await _client.head(uri, headers: nullableHeaderParams,);
+        case 'GET': return await _client.get(uri, headers: nullableHeaderParams,);
+      }
+    } on SocketException catch (error, trace) {
+      throw ApiException.withInner(
+        HttpStatus.badRequest,
+        'Socket operation failed: $method $path',
+        error,
+        trace,
+      );
+    } on TlsException catch (error, trace) {
+      throw ApiException.withInner(
+        HttpStatus.badRequest,
+        'TLS/SSL communication failed: $method $path',
+        error,
+        trace,
+      );
+    } on IOException catch (error, trace) {
+      throw ApiException.withInner(
+        HttpStatus.badRequest,
+        'I/O operation failed: $method $path',
+        error,
+        trace,
+      );
+    } on ClientException catch (error, trace) {
+      throw ApiException.withInner(
+        HttpStatus.badRequest,
+        'HTTP connection failed: $method $path',
+        error,
+        trace,
+      );
+    } on Exception catch (error, trace) {
+      throw ApiException.withInner(
+        HttpStatus.badRequest,
+        'Exception occurred: $method $path',
+        error,
+        trace,
+      );
+    }
+
+    throw ApiException(
+      HttpStatus.badRequest,
+      'Invalid HTTP operation: $method $path',
+    );
+  }
+
+  Future<dynamic> deserializeAsync(String json, String targetType, {bool growable = false,}) async =>
+    // ignore: deprecated_member_use_from_same_package
+    deserialize(json, targetType, growable: growable);
+
+  @Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.')
+  dynamic deserialize(String json, String targetType, {bool growable = false,}) {
+    // Remove all spaces. Necessary for regular expressions as well.
+    targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments
+
+    // If the expected target type is String, nothing to do...
+    return targetType == 'String'
+      ? json
+      : _deserialize(jsonDecode(json), targetType, growable: growable);
+  }
+
+  // ignore: deprecated_member_use_from_same_package
+  Future<String> serializeAsync(Object? value) async => serialize(value);
+
+  @Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use serializeAsync() instead.')
+  String serialize(Object? value) => value == null ? '' : json.encode(value);
+
+  /// Update query and header parameters based on authentication settings.
+  void _updateParamsForAuth(
+    List<QueryParam> queryParams,
+    Map<String, String> headerParams,
+  ) {
+    if (authentication != null) {
+      authentication!.applyToParams(queryParams, headerParams);
+    }
+  }
+
+  static dynamic _deserialize(dynamic value, String targetType, {bool growable = false}) {
+    try {
+      switch (targetType) {
+        case 'String':
+          return value is String ? value : value.toString();
+        case 'int':
+          return value is int ? value : int.parse('$value');
+        case 'double':
+          return value is double ? value : double.parse('$value');
+        case 'bool':
+          if (value is bool) {
+            return value;
+          }
+          final valueString = '$value'.toLowerCase();
+          return valueString == 'true' || valueString == '1';
+        case 'DateTime':
+          return value is DateTime ? value : DateTime.tryParse(value);
+        case 'AddAssetsDto':
+          return AddAssetsDto.fromJson(value);
+        case 'AddUsersDto':
+          return AddUsersDto.fromJson(value);
+        case 'AdminSignupResponseDto':
+          return AdminSignupResponseDto.fromJson(value);
+        case 'AlbumResponseDto':
+          return AlbumResponseDto.fromJson(value);
+        case 'AssetFileUploadResponseDto':
+          return AssetFileUploadResponseDto.fromJson(value);
+        case 'AssetResponseDto':
+          return AssetResponseDto.fromJson(value);
+        case 'AssetTypeEnum':
+          return AssetTypeEnumTypeTransformer().decode(value);
+        case 'CheckDuplicateAssetDto':
+          return CheckDuplicateAssetDto.fromJson(value);
+        case 'CheckDuplicateAssetResponseDto':
+          return CheckDuplicateAssetResponseDto.fromJson(value);
+        case 'CreateAlbumDto':
+          return CreateAlbumDto.fromJson(value);
+        case 'CreateDeviceInfoDto':
+          return CreateDeviceInfoDto.fromJson(value);
+        case 'CreateProfileImageResponseDto':
+          return CreateProfileImageResponseDto.fromJson(value);
+        case 'CreateUserDto':
+          return CreateUserDto.fromJson(value);
+        case 'CuratedLocationsResponseDto':
+          return CuratedLocationsResponseDto.fromJson(value);
+        case 'CuratedObjectsResponseDto':
+          return CuratedObjectsResponseDto.fromJson(value);
+        case 'DeleteAssetDto':
+          return DeleteAssetDto.fromJson(value);
+        case 'DeleteAssetResponseDto':
+          return DeleteAssetResponseDto.fromJson(value);
+        case 'DeleteAssetStatus':
+          return DeleteAssetStatusTypeTransformer().decode(value);
+        case 'DeviceInfoResponseDto':
+          return DeviceInfoResponseDto.fromJson(value);
+        case 'DeviceTypeEnum':
+          return DeviceTypeEnumTypeTransformer().decode(value);
+        case 'ExifResponseDto':
+          return ExifResponseDto.fromJson(value);
+        case 'LoginCredentialDto':
+          return LoginCredentialDto.fromJson(value);
+        case 'LoginResponseDto':
+          return LoginResponseDto.fromJson(value);
+        case 'RemoveAssetsDto':
+          return RemoveAssetsDto.fromJson(value);
+        case 'SearchAssetDto':
+          return SearchAssetDto.fromJson(value);
+        case 'ServerInfoResponseDto':
+          return ServerInfoResponseDto.fromJson(value);
+        case 'ServerPingResponse':
+          return ServerPingResponse.fromJson(value);
+        case 'ServerVersionReponseDto':
+          return ServerVersionReponseDto.fromJson(value);
+        case 'SignUpDto':
+          return SignUpDto.fromJson(value);
+        case 'SmartInfoResponseDto':
+          return SmartInfoResponseDto.fromJson(value);
+        case 'UpdateAlbumDto':
+          return UpdateAlbumDto.fromJson(value);
+        case 'UpdateDeviceInfoDto':
+          return UpdateDeviceInfoDto.fromJson(value);
+        case 'UpdateUserDto':
+          return UpdateUserDto.fromJson(value);
+        case 'UserCountResponseDto':
+          return UserCountResponseDto.fromJson(value);
+        case 'UserResponseDto':
+          return UserResponseDto.fromJson(value);
+        case 'ValidateAccessTokenResponseDto':
+          return ValidateAccessTokenResponseDto.fromJson(value);
+        default:
+          dynamic match;
+          if (value is List && (match = _regList.firstMatch(targetType)?.group(1)) != null) {
+            return value
+              .map<dynamic>((dynamic v) => _deserialize(v, match, growable: growable,))
+              .toList(growable: growable);
+          }
+          if (value is Set && (match = _regSet.firstMatch(targetType)?.group(1)) != null) {
+            return value
+              .map<dynamic>((dynamic v) => _deserialize(v, match, growable: growable,))
+              .toSet();
+          }
+          if (value is Map && (match = _regMap.firstMatch(targetType)?.group(1)) != null) {
+            return Map<String, dynamic>.fromIterables(
+              value.keys.cast<String>(),
+              value.values.map<dynamic>((dynamic v) => _deserialize(v, match, growable: growable,)),
+            );
+          }
+      }
+    } on Exception catch (error, trace) {
+      throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', error, trace,);
+    }
+    throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization',);
+  }
+}
+
+/// Primarily intended for use in an isolate.
+class DeserializationMessage {
+  const DeserializationMessage({
+    required this.json,
+    required this.targetType,
+    this.growable = false,
+  });
+
+  /// The JSON value to deserialize.
+  final String json;
+
+  /// Target type to deserialize to.
+  final String targetType;
+
+  /// Whether to make deserialized lists or maps growable.
+  final bool growable;
+}
+
+/// Primarily intended for use in an isolate.
+Future<dynamic> deserializeAsync(DeserializationMessage message) async {
+  // Remove all spaces. Necessary for regular expressions as well.
+  final targetType = message.targetType.replaceAll(' ', '');
+
+  // If the expected target type is String, nothing to do...
+  return targetType == 'String'
+    ? message.json
+    : ApiClient._deserialize(
+        jsonDecode(message.json),
+        targetType,
+        growable: message.growable,
+      );
+}
+
+/// Primarily intended for use in an isolate.
+Future<String> serializeAsync(Object? value) async => value == null ? '' : json.encode(value);
diff --git a/mobile/openapi/lib/api_exception.dart b/mobile/openapi/lib/api_exception.dart
new file mode 100644
index 0000000000..796f7f7ee7
--- /dev/null
+++ b/mobile/openapi/lib/api_exception.dart
@@ -0,0 +1,33 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ApiException implements Exception {
+  ApiException(this.code, this.message);
+
+  ApiException.withInner(this.code, this.message, this.innerException, this.stackTrace);
+
+  int code = 0;
+  String? message;
+  Exception? innerException;
+  StackTrace? stackTrace;
+
+  @override
+  String toString() {
+    if (message == null) {
+      return 'ApiException';
+    }
+    if (innerException == null) {
+      return 'ApiException $code: $message';
+    }
+    return 'ApiException $code: $message (Inner exception: $innerException)\n\n$stackTrace';
+  }
+}
diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart
new file mode 100644
index 0000000000..325446498a
--- /dev/null
+++ b/mobile/openapi/lib/api_helper.dart
@@ -0,0 +1,110 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class QueryParam {
+  const QueryParam(this.name, this.value);
+
+  final String name;
+  final String value;
+
+  @override
+  String toString() => '${Uri.encodeQueryComponent(name)}=${Uri.encodeQueryComponent(value)}';
+}
+
+// Ported from the Java version.
+Iterable<QueryParam> _queryParams(String collectionFormat, String name, dynamic value,) {
+  // Assertions to run in debug mode only.
+  assert(name.isNotEmpty, 'Parameter cannot be an empty string.');
+
+  final params = <QueryParam>[];
+
+  if (value is List) {
+    if (collectionFormat == 'multi') {
+      return value.map((dynamic v) => QueryParam(name, parameterToString(v)),);
+    }
+
+    // Default collection format is 'csv'.
+    if (collectionFormat.isEmpty) {
+      collectionFormat = 'csv'; // ignore: parameter_assignments
+    }
+
+    final delimiter = _delimiters[collectionFormat] ?? ',';
+
+    params.add(QueryParam(name, value.map<dynamic>(parameterToString).join(delimiter),));
+  } else if (value != null) {
+    params.add(QueryParam(name, parameterToString(value)));
+  }
+
+  return params;
+}
+
+/// Format the given parameter object into a [String].
+String parameterToString(dynamic value) {
+  if (value == null) {
+    return '';
+  }
+  if (value is DateTime) {
+    return value.toUtc().toIso8601String();
+  }
+  if (value is AssetTypeEnum) {
+    return AssetTypeEnumTypeTransformer().encode(value).toString();
+  }
+  if (value is DeleteAssetStatus) {
+    return DeleteAssetStatusTypeTransformer().encode(value).toString();
+  }
+  if (value is DeviceTypeEnum) {
+    return DeviceTypeEnumTypeTransformer().encode(value).toString();
+  }
+  return value.toString();
+}
+
+/// Returns the decoded body as UTF-8 if the given headers indicate an 'application/json'
+/// content type. Otherwise, returns the decoded body as decoded by dart:http package.
+Future<String> _decodeBodyBytes(Response response) async {
+  final contentType = response.headers['content-type'];
+  return contentType != null && contentType.toLowerCase().startsWith('application/json')
+    ? response.bodyBytes.isEmpty ? '' : utf8.decode(response.bodyBytes)
+    : response.body;
+}
+
+/// Returns a valid [T] value found at the specified Map [key], null otherwise.
+T? mapValueOfType<T>(dynamic map, String key) {
+  final dynamic value = map is Map ? map[key] : null;
+  return value is T ? value : null;
+}
+
+/// Returns a valid Map<K, V> found at the specified Map [key], null otherwise.
+Map<K, V>? mapCastOfType<K, V>(dynamic map, String key) {
+  final dynamic value = map is Map ? map[key] : null;
+  return value is Map ? value.cast<K, V>() : null;
+}
+
+/// Returns a valid [DateTime] found at the specified Map [key], null otherwise.
+DateTime? mapDateTime(dynamic map, String key, [String? pattern]) {
+  final dynamic value = map is Map ? map[key] : null;
+  if (value != null) {
+    int? millis;
+    if (value is int) {
+      millis = value;
+    } else if (value is String) {
+      if (pattern == _dateEpochMarker) {
+        millis = int.tryParse(value);
+      } else {
+        return DateTime.tryParse(value);
+      }
+    }
+    if (millis != null) {
+      return DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true);
+    }
+  }
+  return null;
+}
diff --git a/mobile/openapi/lib/auth/api_key_auth.dart b/mobile/openapi/lib/auth/api_key_auth.dart
new file mode 100644
index 0000000000..e304eda321
--- /dev/null
+++ b/mobile/openapi/lib/auth/api_key_auth.dart
@@ -0,0 +1,40 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ApiKeyAuth implements Authentication {
+  ApiKeyAuth(this.location, this.paramName);
+
+  final String location;
+  final String paramName;
+
+  String apiKeyPrefix = '';
+  String apiKey = '';
+
+  @override
+  void applyToParams(List<QueryParam> queryParams, Map<String, String> headerParams) {
+    final paramValue = apiKeyPrefix.isEmpty ? apiKey : '$apiKeyPrefix $apiKey';
+
+    if (paramValue.isNotEmpty) {
+      if (location == 'query') {
+        queryParams.add(QueryParam(paramName, paramValue));
+      } else if (location == 'header') {
+        headerParams[paramName] = paramValue;
+      } else if (location == 'cookie') {
+        headerParams.update(
+          'Cookie',
+          (existingCookie) => '$existingCookie; $paramName=$paramValue',
+          ifAbsent: () => '$paramName=$paramValue',
+        );
+      }
+    }
+  }
+}
diff --git a/mobile/openapi/lib/auth/authentication.dart b/mobile/openapi/lib/auth/authentication.dart
new file mode 100644
index 0000000000..49baf7c46a
--- /dev/null
+++ b/mobile/openapi/lib/auth/authentication.dart
@@ -0,0 +1,17 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+// ignore: one_member_abstracts
+abstract class Authentication {
+  /// Apply authentication settings to header and query params.
+  void applyToParams(List<QueryParam> queryParams, Map<String, String> headerParams);
+}
diff --git a/mobile/openapi/lib/auth/http_basic_auth.dart b/mobile/openapi/lib/auth/http_basic_auth.dart
new file mode 100644
index 0000000000..81abd7185f
--- /dev/null
+++ b/mobile/openapi/lib/auth/http_basic_auth.dart
@@ -0,0 +1,26 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class HttpBasicAuth implements Authentication {
+  HttpBasicAuth({this.username = '', this.password = ''});
+
+  String username;
+  String password;
+
+  @override
+  void applyToParams(List<QueryParam> queryParams, Map<String, String> headerParams) {
+    if (username.isNotEmpty && password.isNotEmpty) {
+      final credentials = '$username:$password';
+      headerParams['Authorization'] = 'Basic ${base64.encode(utf8.encode(credentials))}';
+    }
+  }
+}
diff --git a/mobile/openapi/lib/auth/http_bearer_auth.dart b/mobile/openapi/lib/auth/http_bearer_auth.dart
new file mode 100644
index 0000000000..213f3483b8
--- /dev/null
+++ b/mobile/openapi/lib/auth/http_bearer_auth.dart
@@ -0,0 +1,49 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+typedef HttpBearerAuthProvider = String Function();
+
+class HttpBearerAuth implements Authentication {
+  HttpBearerAuth();
+
+  dynamic _accessToken;
+
+  dynamic get accessToken => _accessToken;
+
+  set accessToken(dynamic accessToken) {
+    if (accessToken is! String && accessToken is! HttpBearerAuthProvider) {
+      throw ArgumentError('accessToken value must be either a String or a String Function().');
+    }
+    _accessToken = accessToken;
+  }
+
+  @override
+  void applyToParams(List<QueryParam> queryParams, Map<String, String> headerParams) {
+    if (_accessToken == null) {
+      return;
+    }
+
+    String accessToken;
+
+    if (_accessToken is String) {
+      accessToken = _accessToken;
+    } else if (_accessToken is HttpBearerAuthProvider) {
+      accessToken = _accessToken!();
+    } else {
+      return;
+    }
+
+    if (accessToken.isNotEmpty) {
+      headerParams['Authorization'] = 'Bearer $accessToken';
+    }
+  }
+}
diff --git a/mobile/openapi/lib/auth/oauth.dart b/mobile/openapi/lib/auth/oauth.dart
new file mode 100644
index 0000000000..e9b87cffb0
--- /dev/null
+++ b/mobile/openapi/lib/auth/oauth.dart
@@ -0,0 +1,24 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class OAuth implements Authentication {
+  OAuth({this.accessToken = ''});
+
+  String accessToken;
+
+  @override
+  void applyToParams(List<QueryParam> queryParams, Map<String, String> headerParams) {
+    if (accessToken.isNotEmpty) {
+      headerParams['Authorization'] = 'Bearer $accessToken';
+    }
+  }
+}
diff --git a/mobile/openapi/lib/model/add_assets_dto.dart b/mobile/openapi/lib/model/add_assets_dto.dart
new file mode 100644
index 0000000000..bebbaeb083
--- /dev/null
+++ b/mobile/openapi/lib/model/add_assets_dto.dart
@@ -0,0 +1,113 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class AddAssetsDto {
+  /// Returns a new [AddAssetsDto] instance.
+  AddAssetsDto({
+    this.assetIds = const [],
+  });
+
+  List<String> assetIds;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is AddAssetsDto &&
+     other.assetIds == assetIds;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (assetIds.hashCode);
+
+  @override
+  String toString() => 'AddAssetsDto[assetIds=$assetIds]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'assetIds'] = assetIds;
+    return _json;
+  }
+
+  /// Returns a new [AddAssetsDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static AddAssetsDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "AddAssetsDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AddAssetsDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return AddAssetsDto(
+        assetIds: json[r'assetIds'] is List
+            ? (json[r'assetIds'] as List).cast<String>()
+            : const [],
+      );
+    }
+    return null;
+  }
+
+  static List<AddAssetsDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AddAssetsDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AddAssetsDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, AddAssetsDto> mapFromJson(dynamic json) {
+    final map = <String, AddAssetsDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AddAssetsDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of AddAssetsDto-objects as value to a dart map
+  static Map<String, List<AddAssetsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<AddAssetsDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AddAssetsDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'assetIds',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/add_users_dto.dart b/mobile/openapi/lib/model/add_users_dto.dart
new file mode 100644
index 0000000000..6e0b5a88f8
--- /dev/null
+++ b/mobile/openapi/lib/model/add_users_dto.dart
@@ -0,0 +1,113 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class AddUsersDto {
+  /// Returns a new [AddUsersDto] instance.
+  AddUsersDto({
+    this.sharedUserIds = const [],
+  });
+
+  List<String> sharedUserIds;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is AddUsersDto &&
+     other.sharedUserIds == sharedUserIds;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (sharedUserIds.hashCode);
+
+  @override
+  String toString() => 'AddUsersDto[sharedUserIds=$sharedUserIds]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'sharedUserIds'] = sharedUserIds;
+    return _json;
+  }
+
+  /// Returns a new [AddUsersDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static AddUsersDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "AddUsersDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AddUsersDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return AddUsersDto(
+        sharedUserIds: json[r'sharedUserIds'] is List
+            ? (json[r'sharedUserIds'] as List).cast<String>()
+            : const [],
+      );
+    }
+    return null;
+  }
+
+  static List<AddUsersDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AddUsersDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AddUsersDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, AddUsersDto> mapFromJson(dynamic json) {
+    final map = <String, AddUsersDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AddUsersDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of AddUsersDto-objects as value to a dart map
+  static Map<String, List<AddUsersDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<AddUsersDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AddUsersDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'sharedUserIds',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/admin_signup_response_dto.dart b/mobile/openapi/lib/model/admin_signup_response_dto.dart
new file mode 100644
index 0000000000..8757449690
--- /dev/null
+++ b/mobile/openapi/lib/model/admin_signup_response_dto.dart
@@ -0,0 +1,143 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class AdminSignupResponseDto {
+  /// Returns a new [AdminSignupResponseDto] instance.
+  AdminSignupResponseDto({
+    required this.id,
+    required this.email,
+    required this.firstName,
+    required this.lastName,
+    required this.createdAt,
+  });
+
+  String id;
+
+  String email;
+
+  String firstName;
+
+  String lastName;
+
+  String createdAt;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is AdminSignupResponseDto &&
+     other.id == id &&
+     other.email == email &&
+     other.firstName == firstName &&
+     other.lastName == lastName &&
+     other.createdAt == createdAt;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode) +
+    (email.hashCode) +
+    (firstName.hashCode) +
+    (lastName.hashCode) +
+    (createdAt.hashCode);
+
+  @override
+  String toString() => 'AdminSignupResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+      _json[r'email'] = email;
+      _json[r'firstName'] = firstName;
+      _json[r'lastName'] = lastName;
+      _json[r'createdAt'] = createdAt;
+    return _json;
+  }
+
+  /// Returns a new [AdminSignupResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static AdminSignupResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "AdminSignupResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AdminSignupResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return AdminSignupResponseDto(
+        id: mapValueOfType<String>(json, r'id')!,
+        email: mapValueOfType<String>(json, r'email')!,
+        firstName: mapValueOfType<String>(json, r'firstName')!,
+        lastName: mapValueOfType<String>(json, r'lastName')!,
+        createdAt: mapValueOfType<String>(json, r'createdAt')!,
+      );
+    }
+    return null;
+  }
+
+  static List<AdminSignupResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AdminSignupResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AdminSignupResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, AdminSignupResponseDto> mapFromJson(dynamic json) {
+    final map = <String, AdminSignupResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AdminSignupResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of AdminSignupResponseDto-objects as value to a dart map
+  static Map<String, List<AdminSignupResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<AdminSignupResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AdminSignupResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+    'email',
+    'firstName',
+    'lastName',
+    'createdAt',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart
new file mode 100644
index 0000000000..80ee699e49
--- /dev/null
+++ b/mobile/openapi/lib/model/album_response_dto.dart
@@ -0,0 +1,171 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class AlbumResponseDto {
+  /// Returns a new [AlbumResponseDto] instance.
+  AlbumResponseDto({
+    required this.id,
+    required this.ownerId,
+    required this.albumName,
+    required this.createdAt,
+    required this.albumThumbnailAssetId,
+    required this.shared,
+    this.sharedUsers = const [],
+    this.assets = const [],
+  });
+
+  String id;
+
+  String ownerId;
+
+  String albumName;
+
+  String createdAt;
+
+  String? albumThumbnailAssetId;
+
+  bool shared;
+
+  List<UserResponseDto> sharedUsers;
+
+  List<AssetResponseDto> assets;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
+     other.id == id &&
+     other.ownerId == ownerId &&
+     other.albumName == albumName &&
+     other.createdAt == createdAt &&
+     other.albumThumbnailAssetId == albumThumbnailAssetId &&
+     other.shared == shared &&
+     other.sharedUsers == sharedUsers &&
+     other.assets == assets;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode) +
+    (ownerId.hashCode) +
+    (albumName.hashCode) +
+    (createdAt.hashCode) +
+    (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
+    (shared.hashCode) +
+    (sharedUsers.hashCode) +
+    (assets.hashCode);
+
+  @override
+  String toString() => 'AlbumResponseDto[id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+      _json[r'ownerId'] = ownerId;
+      _json[r'albumName'] = albumName;
+      _json[r'createdAt'] = createdAt;
+    if (albumThumbnailAssetId != null) {
+      _json[r'albumThumbnailAssetId'] = albumThumbnailAssetId;
+    } else {
+      _json[r'albumThumbnailAssetId'] = null;
+    }
+      _json[r'shared'] = shared;
+      _json[r'sharedUsers'] = sharedUsers;
+      _json[r'assets'] = assets;
+    return _json;
+  }
+
+  /// Returns a new [AlbumResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static AlbumResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return AlbumResponseDto(
+        id: mapValueOfType<String>(json, r'id')!,
+        ownerId: mapValueOfType<String>(json, r'ownerId')!,
+        albumName: mapValueOfType<String>(json, r'albumName')!,
+        createdAt: mapValueOfType<String>(json, r'createdAt')!,
+        albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
+        shared: mapValueOfType<bool>(json, r'shared')!,
+        sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
+        assets: AssetResponseDto.listFromJson(json[r'assets'])!,
+      );
+    }
+    return null;
+  }
+
+  static List<AlbumResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AlbumResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AlbumResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, AlbumResponseDto> mapFromJson(dynamic json) {
+    final map = <String, AlbumResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AlbumResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of AlbumResponseDto-objects as value to a dart map
+  static Map<String, List<AlbumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<AlbumResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+    'ownerId',
+    'albumName',
+    'createdAt',
+    'albumThumbnailAssetId',
+    'shared',
+    'sharedUsers',
+    'assets',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/asset_file_upload_response_dto.dart b/mobile/openapi/lib/model/asset_file_upload_response_dto.dart
new file mode 100644
index 0000000000..40583b6ce5
--- /dev/null
+++ b/mobile/openapi/lib/model/asset_file_upload_response_dto.dart
@@ -0,0 +1,111 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class AssetFileUploadResponseDto {
+  /// Returns a new [AssetFileUploadResponseDto] instance.
+  AssetFileUploadResponseDto({
+    required this.id,
+  });
+
+  String id;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is AssetFileUploadResponseDto &&
+     other.id == id;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode);
+
+  @override
+  String toString() => 'AssetFileUploadResponseDto[id=$id]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+    return _json;
+  }
+
+  /// Returns a new [AssetFileUploadResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static AssetFileUploadResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "AssetFileUploadResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AssetFileUploadResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return AssetFileUploadResponseDto(
+        id: mapValueOfType<String>(json, r'id')!,
+      );
+    }
+    return null;
+  }
+
+  static List<AssetFileUploadResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AssetFileUploadResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AssetFileUploadResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, AssetFileUploadResponseDto> mapFromJson(dynamic json) {
+    final map = <String, AssetFileUploadResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AssetFileUploadResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of AssetFileUploadResponseDto-objects as value to a dart map
+  static Map<String, List<AssetFileUploadResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<AssetFileUploadResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AssetFileUploadResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart
new file mode 100644
index 0000000000..cd1e83c5f2
--- /dev/null
+++ b/mobile/openapi/lib/model/asset_response_dto.dart
@@ -0,0 +1,265 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class AssetResponseDto {
+  /// Returns a new [AssetResponseDto] instance.
+  AssetResponseDto({
+    required this.type,
+    required this.id,
+    required this.deviceAssetId,
+    required this.ownerId,
+    required this.deviceId,
+    required this.originalPath,
+    required this.resizePath,
+    required this.createdAt,
+    required this.modifiedAt,
+    required this.isFavorite,
+    required this.mimeType,
+    required this.duration,
+    required this.webpPath,
+    required this.encodedVideoPath,
+    this.exifInfo,
+    this.smartInfo,
+  });
+
+  AssetTypeEnum type;
+
+  String id;
+
+  String deviceAssetId;
+
+  String ownerId;
+
+  String deviceId;
+
+  String originalPath;
+
+  String? resizePath;
+
+  String createdAt;
+
+  String modifiedAt;
+
+  bool isFavorite;
+
+  String? mimeType;
+
+  String duration;
+
+  String? webpPath;
+
+  String? encodedVideoPath;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  ExifResponseDto? exifInfo;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  SmartInfoResponseDto? smartInfo;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
+     other.type == type &&
+     other.id == id &&
+     other.deviceAssetId == deviceAssetId &&
+     other.ownerId == ownerId &&
+     other.deviceId == deviceId &&
+     other.originalPath == originalPath &&
+     other.resizePath == resizePath &&
+     other.createdAt == createdAt &&
+     other.modifiedAt == modifiedAt &&
+     other.isFavorite == isFavorite &&
+     other.mimeType == mimeType &&
+     other.duration == duration &&
+     other.webpPath == webpPath &&
+     other.encodedVideoPath == encodedVideoPath &&
+     other.exifInfo == exifInfo &&
+     other.smartInfo == smartInfo;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (type.hashCode) +
+    (id.hashCode) +
+    (deviceAssetId.hashCode) +
+    (ownerId.hashCode) +
+    (deviceId.hashCode) +
+    (originalPath.hashCode) +
+    (resizePath == null ? 0 : resizePath!.hashCode) +
+    (createdAt.hashCode) +
+    (modifiedAt.hashCode) +
+    (isFavorite.hashCode) +
+    (mimeType == null ? 0 : mimeType!.hashCode) +
+    (duration.hashCode) +
+    (webpPath == null ? 0 : webpPath!.hashCode) +
+    (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
+    (exifInfo == null ? 0 : exifInfo!.hashCode) +
+    (smartInfo == null ? 0 : smartInfo!.hashCode);
+
+  @override
+  String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'type'] = type;
+      _json[r'id'] = id;
+      _json[r'deviceAssetId'] = deviceAssetId;
+      _json[r'ownerId'] = ownerId;
+      _json[r'deviceId'] = deviceId;
+      _json[r'originalPath'] = originalPath;
+    if (resizePath != null) {
+      _json[r'resizePath'] = resizePath;
+    } else {
+      _json[r'resizePath'] = null;
+    }
+      _json[r'createdAt'] = createdAt;
+      _json[r'modifiedAt'] = modifiedAt;
+      _json[r'isFavorite'] = isFavorite;
+    if (mimeType != null) {
+      _json[r'mimeType'] = mimeType;
+    } else {
+      _json[r'mimeType'] = null;
+    }
+      _json[r'duration'] = duration;
+    if (webpPath != null) {
+      _json[r'webpPath'] = webpPath;
+    } else {
+      _json[r'webpPath'] = null;
+    }
+    if (encodedVideoPath != null) {
+      _json[r'encodedVideoPath'] = encodedVideoPath;
+    } else {
+      _json[r'encodedVideoPath'] = null;
+    }
+    if (exifInfo != null) {
+      _json[r'exifInfo'] = exifInfo;
+    } else {
+      _json[r'exifInfo'] = null;
+    }
+    if (smartInfo != null) {
+      _json[r'smartInfo'] = smartInfo;
+    } else {
+      _json[r'smartInfo'] = null;
+    }
+    return _json;
+  }
+
+  /// Returns a new [AssetResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static AssetResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return AssetResponseDto(
+        type: AssetTypeEnum.fromJson(json[r'type'])!,
+        id: mapValueOfType<String>(json, r'id')!,
+        deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
+        ownerId: mapValueOfType<String>(json, r'ownerId')!,
+        deviceId: mapValueOfType<String>(json, r'deviceId')!,
+        originalPath: mapValueOfType<String>(json, r'originalPath')!,
+        resizePath: mapValueOfType<String>(json, r'resizePath'),
+        createdAt: mapValueOfType<String>(json, r'createdAt')!,
+        modifiedAt: mapValueOfType<String>(json, r'modifiedAt')!,
+        isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
+        mimeType: mapValueOfType<String>(json, r'mimeType'),
+        duration: mapValueOfType<String>(json, r'duration')!,
+        webpPath: mapValueOfType<String>(json, r'webpPath'),
+        encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'),
+        exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
+        smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
+      );
+    }
+    return null;
+  }
+
+  static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AssetResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AssetResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, AssetResponseDto> mapFromJson(dynamic json) {
+    final map = <String, AssetResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AssetResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of AssetResponseDto-objects as value to a dart map
+  static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<AssetResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'type',
+    'id',
+    'deviceAssetId',
+    'ownerId',
+    'deviceId',
+    'originalPath',
+    'resizePath',
+    'createdAt',
+    'modifiedAt',
+    'isFavorite',
+    'mimeType',
+    'duration',
+    'webpPath',
+    'encodedVideoPath',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/asset_type_enum.dart b/mobile/openapi/lib/model/asset_type_enum.dart
new file mode 100644
index 0000000000..bf1d3ba193
--- /dev/null
+++ b/mobile/openapi/lib/model/asset_type_enum.dart
@@ -0,0 +1,91 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class AssetTypeEnum {
+  /// Instantiate a new enum with the provided [value].
+  const AssetTypeEnum._(this.value);
+
+  /// The underlying value of this enum member.
+  final String value;
+
+  @override
+  String toString() => value;
+
+  String toJson() => value;
+
+  static const IMAGE = AssetTypeEnum._(r'IMAGE');
+  static const VIDEO = AssetTypeEnum._(r'VIDEO');
+  static const AUDIO = AssetTypeEnum._(r'AUDIO');
+  static const OTHER = AssetTypeEnum._(r'OTHER');
+
+  /// List of all possible values in this [enum][AssetTypeEnum].
+  static const values = <AssetTypeEnum>[
+    IMAGE,
+    VIDEO,
+    AUDIO,
+    OTHER,
+  ];
+
+  static AssetTypeEnum? fromJson(dynamic value) => AssetTypeEnumTypeTransformer().decode(value);
+
+  static List<AssetTypeEnum>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <AssetTypeEnum>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = AssetTypeEnum.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+}
+
+/// Transformation class that can [encode] an instance of [AssetTypeEnum] to String,
+/// and [decode] dynamic data back to [AssetTypeEnum].
+class AssetTypeEnumTypeTransformer {
+  factory AssetTypeEnumTypeTransformer() => _instance ??= const AssetTypeEnumTypeTransformer._();
+
+  const AssetTypeEnumTypeTransformer._();
+
+  String encode(AssetTypeEnum data) => data.value;
+
+  /// Decodes a [dynamic value][data] to a AssetTypeEnum.
+  ///
+  /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
+  /// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
+  /// cannot be decoded successfully, then an [UnimplementedError] is thrown.
+  ///
+  /// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
+  /// and users are still using an old app with the old code.
+  AssetTypeEnum? decode(dynamic data, {bool allowNull = true}) {
+    if (data != null) {
+      switch (data.toString()) {
+        case r'IMAGE': return AssetTypeEnum.IMAGE;
+        case r'VIDEO': return AssetTypeEnum.VIDEO;
+        case r'AUDIO': return AssetTypeEnum.AUDIO;
+        case r'OTHER': return AssetTypeEnum.OTHER;
+        default:
+          if (!allowNull) {
+            throw ArgumentError('Unknown enum value to decode: $data');
+          }
+      }
+    }
+    return null;
+  }
+
+  /// Singleton [AssetTypeEnumTypeTransformer] instance.
+  static AssetTypeEnumTypeTransformer? _instance;
+}
+
diff --git a/mobile/openapi/lib/model/check_duplicate_asset_dto.dart b/mobile/openapi/lib/model/check_duplicate_asset_dto.dart
new file mode 100644
index 0000000000..8644381cbf
--- /dev/null
+++ b/mobile/openapi/lib/model/check_duplicate_asset_dto.dart
@@ -0,0 +1,119 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CheckDuplicateAssetDto {
+  /// Returns a new [CheckDuplicateAssetDto] instance.
+  CheckDuplicateAssetDto({
+    required this.deviceAssetId,
+    required this.deviceId,
+  });
+
+  String deviceAssetId;
+
+  String deviceId;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CheckDuplicateAssetDto &&
+     other.deviceAssetId == deviceAssetId &&
+     other.deviceId == deviceId;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (deviceAssetId.hashCode) +
+    (deviceId.hashCode);
+
+  @override
+  String toString() => 'CheckDuplicateAssetDto[deviceAssetId=$deviceAssetId, deviceId=$deviceId]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'deviceAssetId'] = deviceAssetId;
+      _json[r'deviceId'] = deviceId;
+    return _json;
+  }
+
+  /// Returns a new [CheckDuplicateAssetDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CheckDuplicateAssetDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CheckDuplicateAssetDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CheckDuplicateAssetDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CheckDuplicateAssetDto(
+        deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
+        deviceId: mapValueOfType<String>(json, r'deviceId')!,
+      );
+    }
+    return null;
+  }
+
+  static List<CheckDuplicateAssetDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CheckDuplicateAssetDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CheckDuplicateAssetDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CheckDuplicateAssetDto> mapFromJson(dynamic json) {
+    final map = <String, CheckDuplicateAssetDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CheckDuplicateAssetDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CheckDuplicateAssetDto-objects as value to a dart map
+  static Map<String, List<CheckDuplicateAssetDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CheckDuplicateAssetDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CheckDuplicateAssetDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'deviceAssetId',
+    'deviceId',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/check_duplicate_asset_response_dto.dart b/mobile/openapi/lib/model/check_duplicate_asset_response_dto.dart
new file mode 100644
index 0000000000..5c5453fd8e
--- /dev/null
+++ b/mobile/openapi/lib/model/check_duplicate_asset_response_dto.dart
@@ -0,0 +1,111 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CheckDuplicateAssetResponseDto {
+  /// Returns a new [CheckDuplicateAssetResponseDto] instance.
+  CheckDuplicateAssetResponseDto({
+    required this.isExist,
+  });
+
+  bool isExist;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CheckDuplicateAssetResponseDto &&
+     other.isExist == isExist;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (isExist.hashCode);
+
+  @override
+  String toString() => 'CheckDuplicateAssetResponseDto[isExist=$isExist]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'isExist'] = isExist;
+    return _json;
+  }
+
+  /// Returns a new [CheckDuplicateAssetResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CheckDuplicateAssetResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CheckDuplicateAssetResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CheckDuplicateAssetResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CheckDuplicateAssetResponseDto(
+        isExist: mapValueOfType<bool>(json, r'isExist')!,
+      );
+    }
+    return null;
+  }
+
+  static List<CheckDuplicateAssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CheckDuplicateAssetResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CheckDuplicateAssetResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CheckDuplicateAssetResponseDto> mapFromJson(dynamic json) {
+    final map = <String, CheckDuplicateAssetResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CheckDuplicateAssetResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CheckDuplicateAssetResponseDto-objects as value to a dart map
+  static Map<String, List<CheckDuplicateAssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CheckDuplicateAssetResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CheckDuplicateAssetResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'isExist',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/create_album_dto.dart b/mobile/openapi/lib/model/create_album_dto.dart
new file mode 100644
index 0000000000..202a474be7
--- /dev/null
+++ b/mobile/openapi/lib/model/create_album_dto.dart
@@ -0,0 +1,129 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CreateAlbumDto {
+  /// Returns a new [CreateAlbumDto] instance.
+  CreateAlbumDto({
+    required this.albumName,
+    this.sharedWithUserIds = const [],
+    this.assetIds = const [],
+  });
+
+  String albumName;
+
+  List<String> sharedWithUserIds;
+
+  List<String> assetIds;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CreateAlbumDto &&
+     other.albumName == albumName &&
+     other.sharedWithUserIds == sharedWithUserIds &&
+     other.assetIds == assetIds;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (albumName.hashCode) +
+    (sharedWithUserIds.hashCode) +
+    (assetIds.hashCode);
+
+  @override
+  String toString() => 'CreateAlbumDto[albumName=$albumName, sharedWithUserIds=$sharedWithUserIds, assetIds=$assetIds]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'albumName'] = albumName;
+      _json[r'sharedWithUserIds'] = sharedWithUserIds;
+      _json[r'assetIds'] = assetIds;
+    return _json;
+  }
+
+  /// Returns a new [CreateAlbumDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CreateAlbumDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CreateAlbumDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CreateAlbumDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CreateAlbumDto(
+        albumName: mapValueOfType<String>(json, r'albumName')!,
+        sharedWithUserIds: json[r'sharedWithUserIds'] is List
+            ? (json[r'sharedWithUserIds'] as List).cast<String>()
+            : const [],
+        assetIds: json[r'assetIds'] is List
+            ? (json[r'assetIds'] as List).cast<String>()
+            : const [],
+      );
+    }
+    return null;
+  }
+
+  static List<CreateAlbumDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CreateAlbumDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CreateAlbumDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CreateAlbumDto> mapFromJson(dynamic json) {
+    final map = <String, CreateAlbumDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateAlbumDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CreateAlbumDto-objects as value to a dart map
+  static Map<String, List<CreateAlbumDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CreateAlbumDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateAlbumDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'albumName',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/create_device_info_dto.dart b/mobile/openapi/lib/model/create_device_info_dto.dart
new file mode 100644
index 0000000000..1c2cf46e16
--- /dev/null
+++ b/mobile/openapi/lib/model/create_device_info_dto.dart
@@ -0,0 +1,136 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CreateDeviceInfoDto {
+  /// Returns a new [CreateDeviceInfoDto] instance.
+  CreateDeviceInfoDto({
+    required this.deviceType,
+    required this.deviceId,
+    this.isAutoBackup,
+  });
+
+  DeviceTypeEnum deviceType;
+
+  String deviceId;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  bool? isAutoBackup;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CreateDeviceInfoDto &&
+     other.deviceType == deviceType &&
+     other.deviceId == deviceId &&
+     other.isAutoBackup == isAutoBackup;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (deviceType.hashCode) +
+    (deviceId.hashCode) +
+    (isAutoBackup == null ? 0 : isAutoBackup!.hashCode);
+
+  @override
+  String toString() => 'CreateDeviceInfoDto[deviceType=$deviceType, deviceId=$deviceId, isAutoBackup=$isAutoBackup]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'deviceType'] = deviceType;
+      _json[r'deviceId'] = deviceId;
+    if (isAutoBackup != null) {
+      _json[r'isAutoBackup'] = isAutoBackup;
+    } else {
+      _json[r'isAutoBackup'] = null;
+    }
+    return _json;
+  }
+
+  /// Returns a new [CreateDeviceInfoDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CreateDeviceInfoDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CreateDeviceInfoDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CreateDeviceInfoDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CreateDeviceInfoDto(
+        deviceType: DeviceTypeEnum.fromJson(json[r'deviceType'])!,
+        deviceId: mapValueOfType<String>(json, r'deviceId')!,
+        isAutoBackup: mapValueOfType<bool>(json, r'isAutoBackup'),
+      );
+    }
+    return null;
+  }
+
+  static List<CreateDeviceInfoDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CreateDeviceInfoDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CreateDeviceInfoDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CreateDeviceInfoDto> mapFromJson(dynamic json) {
+    final map = <String, CreateDeviceInfoDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateDeviceInfoDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CreateDeviceInfoDto-objects as value to a dart map
+  static Map<String, List<CreateDeviceInfoDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CreateDeviceInfoDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateDeviceInfoDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'deviceType',
+    'deviceId',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/create_profile_image_response_dto.dart b/mobile/openapi/lib/model/create_profile_image_response_dto.dart
new file mode 100644
index 0000000000..fc7321cbb8
--- /dev/null
+++ b/mobile/openapi/lib/model/create_profile_image_response_dto.dart
@@ -0,0 +1,119 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CreateProfileImageResponseDto {
+  /// Returns a new [CreateProfileImageResponseDto] instance.
+  CreateProfileImageResponseDto({
+    required this.userId,
+    required this.profileImagePath,
+  });
+
+  String userId;
+
+  String profileImagePath;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CreateProfileImageResponseDto &&
+     other.userId == userId &&
+     other.profileImagePath == profileImagePath;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (userId.hashCode) +
+    (profileImagePath.hashCode);
+
+  @override
+  String toString() => 'CreateProfileImageResponseDto[userId=$userId, profileImagePath=$profileImagePath]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'userId'] = userId;
+      _json[r'profileImagePath'] = profileImagePath;
+    return _json;
+  }
+
+  /// Returns a new [CreateProfileImageResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CreateProfileImageResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CreateProfileImageResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CreateProfileImageResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CreateProfileImageResponseDto(
+        userId: mapValueOfType<String>(json, r'userId')!,
+        profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
+      );
+    }
+    return null;
+  }
+
+  static List<CreateProfileImageResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CreateProfileImageResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CreateProfileImageResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CreateProfileImageResponseDto> mapFromJson(dynamic json) {
+    final map = <String, CreateProfileImageResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateProfileImageResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CreateProfileImageResponseDto-objects as value to a dart map
+  static Map<String, List<CreateProfileImageResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CreateProfileImageResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateProfileImageResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'userId',
+    'profileImagePath',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/create_user_dto.dart b/mobile/openapi/lib/model/create_user_dto.dart
new file mode 100644
index 0000000000..b8977d46d9
--- /dev/null
+++ b/mobile/openapi/lib/model/create_user_dto.dart
@@ -0,0 +1,135 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CreateUserDto {
+  /// Returns a new [CreateUserDto] instance.
+  CreateUserDto({
+    required this.email,
+    required this.password,
+    required this.firstName,
+    required this.lastName,
+  });
+
+  String email;
+
+  String password;
+
+  String firstName;
+
+  String lastName;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CreateUserDto &&
+     other.email == email &&
+     other.password == password &&
+     other.firstName == firstName &&
+     other.lastName == lastName;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (email.hashCode) +
+    (password.hashCode) +
+    (firstName.hashCode) +
+    (lastName.hashCode);
+
+  @override
+  String toString() => 'CreateUserDto[email=$email, password=$password, firstName=$firstName, lastName=$lastName]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'email'] = email;
+      _json[r'password'] = password;
+      _json[r'firstName'] = firstName;
+      _json[r'lastName'] = lastName;
+    return _json;
+  }
+
+  /// Returns a new [CreateUserDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CreateUserDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CreateUserDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CreateUserDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CreateUserDto(
+        email: mapValueOfType<String>(json, r'email')!,
+        password: mapValueOfType<String>(json, r'password')!,
+        firstName: mapValueOfType<String>(json, r'firstName')!,
+        lastName: mapValueOfType<String>(json, r'lastName')!,
+      );
+    }
+    return null;
+  }
+
+  static List<CreateUserDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CreateUserDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CreateUserDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CreateUserDto> mapFromJson(dynamic json) {
+    final map = <String, CreateUserDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateUserDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CreateUserDto-objects as value to a dart map
+  static Map<String, List<CreateUserDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CreateUserDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CreateUserDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'email',
+    'password',
+    'firstName',
+    'lastName',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/curated_locations_response_dto.dart b/mobile/openapi/lib/model/curated_locations_response_dto.dart
new file mode 100644
index 0000000000..5117aa9908
--- /dev/null
+++ b/mobile/openapi/lib/model/curated_locations_response_dto.dart
@@ -0,0 +1,143 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CuratedLocationsResponseDto {
+  /// Returns a new [CuratedLocationsResponseDto] instance.
+  CuratedLocationsResponseDto({
+    required this.id,
+    required this.city,
+    required this.resizePath,
+    required this.deviceAssetId,
+    required this.deviceId,
+  });
+
+  String id;
+
+  String city;
+
+  String resizePath;
+
+  String deviceAssetId;
+
+  String deviceId;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CuratedLocationsResponseDto &&
+     other.id == id &&
+     other.city == city &&
+     other.resizePath == resizePath &&
+     other.deviceAssetId == deviceAssetId &&
+     other.deviceId == deviceId;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode) +
+    (city.hashCode) +
+    (resizePath.hashCode) +
+    (deviceAssetId.hashCode) +
+    (deviceId.hashCode);
+
+  @override
+  String toString() => 'CuratedLocationsResponseDto[id=$id, city=$city, resizePath=$resizePath, deviceAssetId=$deviceAssetId, deviceId=$deviceId]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+      _json[r'city'] = city;
+      _json[r'resizePath'] = resizePath;
+      _json[r'deviceAssetId'] = deviceAssetId;
+      _json[r'deviceId'] = deviceId;
+    return _json;
+  }
+
+  /// Returns a new [CuratedLocationsResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CuratedLocationsResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CuratedLocationsResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CuratedLocationsResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CuratedLocationsResponseDto(
+        id: mapValueOfType<String>(json, r'id')!,
+        city: mapValueOfType<String>(json, r'city')!,
+        resizePath: mapValueOfType<String>(json, r'resizePath')!,
+        deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
+        deviceId: mapValueOfType<String>(json, r'deviceId')!,
+      );
+    }
+    return null;
+  }
+
+  static List<CuratedLocationsResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CuratedLocationsResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CuratedLocationsResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CuratedLocationsResponseDto> mapFromJson(dynamic json) {
+    final map = <String, CuratedLocationsResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CuratedLocationsResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CuratedLocationsResponseDto-objects as value to a dart map
+  static Map<String, List<CuratedLocationsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CuratedLocationsResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CuratedLocationsResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+    'city',
+    'resizePath',
+    'deviceAssetId',
+    'deviceId',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/curated_objects_response_dto.dart b/mobile/openapi/lib/model/curated_objects_response_dto.dart
new file mode 100644
index 0000000000..00662c5f17
--- /dev/null
+++ b/mobile/openapi/lib/model/curated_objects_response_dto.dart
@@ -0,0 +1,143 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class CuratedObjectsResponseDto {
+  /// Returns a new [CuratedObjectsResponseDto] instance.
+  CuratedObjectsResponseDto({
+    required this.id,
+    required this.object,
+    required this.resizePath,
+    required this.deviceAssetId,
+    required this.deviceId,
+  });
+
+  String id;
+
+  String object;
+
+  String resizePath;
+
+  String deviceAssetId;
+
+  String deviceId;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is CuratedObjectsResponseDto &&
+     other.id == id &&
+     other.object == object &&
+     other.resizePath == resizePath &&
+     other.deviceAssetId == deviceAssetId &&
+     other.deviceId == deviceId;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode) +
+    (object.hashCode) +
+    (resizePath.hashCode) +
+    (deviceAssetId.hashCode) +
+    (deviceId.hashCode);
+
+  @override
+  String toString() => 'CuratedObjectsResponseDto[id=$id, object=$object, resizePath=$resizePath, deviceAssetId=$deviceAssetId, deviceId=$deviceId]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+      _json[r'object'] = object;
+      _json[r'resizePath'] = resizePath;
+      _json[r'deviceAssetId'] = deviceAssetId;
+      _json[r'deviceId'] = deviceId;
+    return _json;
+  }
+
+  /// Returns a new [CuratedObjectsResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static CuratedObjectsResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "CuratedObjectsResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "CuratedObjectsResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return CuratedObjectsResponseDto(
+        id: mapValueOfType<String>(json, r'id')!,
+        object: mapValueOfType<String>(json, r'object')!,
+        resizePath: mapValueOfType<String>(json, r'resizePath')!,
+        deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
+        deviceId: mapValueOfType<String>(json, r'deviceId')!,
+      );
+    }
+    return null;
+  }
+
+  static List<CuratedObjectsResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <CuratedObjectsResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = CuratedObjectsResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, CuratedObjectsResponseDto> mapFromJson(dynamic json) {
+    final map = <String, CuratedObjectsResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CuratedObjectsResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of CuratedObjectsResponseDto-objects as value to a dart map
+  static Map<String, List<CuratedObjectsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<CuratedObjectsResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = CuratedObjectsResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+    'object',
+    'resizePath',
+    'deviceAssetId',
+    'deviceId',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/delete_asset_dto.dart b/mobile/openapi/lib/model/delete_asset_dto.dart
new file mode 100644
index 0000000000..4830dd40c3
--- /dev/null
+++ b/mobile/openapi/lib/model/delete_asset_dto.dart
@@ -0,0 +1,113 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class DeleteAssetDto {
+  /// Returns a new [DeleteAssetDto] instance.
+  DeleteAssetDto({
+    this.ids = const [],
+  });
+
+  List<String> ids;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is DeleteAssetDto &&
+     other.ids == ids;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (ids.hashCode);
+
+  @override
+  String toString() => 'DeleteAssetDto[ids=$ids]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'ids'] = ids;
+    return _json;
+  }
+
+  /// Returns a new [DeleteAssetDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static DeleteAssetDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "DeleteAssetDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "DeleteAssetDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return DeleteAssetDto(
+        ids: json[r'ids'] is List
+            ? (json[r'ids'] as List).cast<String>()
+            : const [],
+      );
+    }
+    return null;
+  }
+
+  static List<DeleteAssetDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <DeleteAssetDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = DeleteAssetDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, DeleteAssetDto> mapFromJson(dynamic json) {
+    final map = <String, DeleteAssetDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = DeleteAssetDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of DeleteAssetDto-objects as value to a dart map
+  static Map<String, List<DeleteAssetDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<DeleteAssetDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = DeleteAssetDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'ids',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/delete_asset_response_dto.dart b/mobile/openapi/lib/model/delete_asset_response_dto.dart
new file mode 100644
index 0000000000..b71336aca8
--- /dev/null
+++ b/mobile/openapi/lib/model/delete_asset_response_dto.dart
@@ -0,0 +1,119 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class DeleteAssetResponseDto {
+  /// Returns a new [DeleteAssetResponseDto] instance.
+  DeleteAssetResponseDto({
+    required this.status,
+    required this.id,
+  });
+
+  DeleteAssetStatus status;
+
+  String id;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is DeleteAssetResponseDto &&
+     other.status == status &&
+     other.id == id;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (status.hashCode) +
+    (id.hashCode);
+
+  @override
+  String toString() => 'DeleteAssetResponseDto[status=$status, id=$id]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'status'] = status;
+      _json[r'id'] = id;
+    return _json;
+  }
+
+  /// Returns a new [DeleteAssetResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static DeleteAssetResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "DeleteAssetResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "DeleteAssetResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return DeleteAssetResponseDto(
+        status: DeleteAssetStatus.fromJson(json[r'status'])!,
+        id: mapValueOfType<String>(json, r'id')!,
+      );
+    }
+    return null;
+  }
+
+  static List<DeleteAssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <DeleteAssetResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = DeleteAssetResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, DeleteAssetResponseDto> mapFromJson(dynamic json) {
+    final map = <String, DeleteAssetResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = DeleteAssetResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of DeleteAssetResponseDto-objects as value to a dart map
+  static Map<String, List<DeleteAssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<DeleteAssetResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = DeleteAssetResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'status',
+    'id',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/delete_asset_status.dart b/mobile/openapi/lib/model/delete_asset_status.dart
new file mode 100644
index 0000000000..045115aa52
--- /dev/null
+++ b/mobile/openapi/lib/model/delete_asset_status.dart
@@ -0,0 +1,85 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class DeleteAssetStatus {
+  /// Instantiate a new enum with the provided [value].
+  const DeleteAssetStatus._(this.value);
+
+  /// The underlying value of this enum member.
+  final String value;
+
+  @override
+  String toString() => value;
+
+  String toJson() => value;
+
+  static const SUCCESS = DeleteAssetStatus._(r'SUCCESS');
+  static const FAILED = DeleteAssetStatus._(r'FAILED');
+
+  /// List of all possible values in this [enum][DeleteAssetStatus].
+  static const values = <DeleteAssetStatus>[
+    SUCCESS,
+    FAILED,
+  ];
+
+  static DeleteAssetStatus? fromJson(dynamic value) => DeleteAssetStatusTypeTransformer().decode(value);
+
+  static List<DeleteAssetStatus>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <DeleteAssetStatus>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = DeleteAssetStatus.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+}
+
+/// Transformation class that can [encode] an instance of [DeleteAssetStatus] to String,
+/// and [decode] dynamic data back to [DeleteAssetStatus].
+class DeleteAssetStatusTypeTransformer {
+  factory DeleteAssetStatusTypeTransformer() => _instance ??= const DeleteAssetStatusTypeTransformer._();
+
+  const DeleteAssetStatusTypeTransformer._();
+
+  String encode(DeleteAssetStatus data) => data.value;
+
+  /// Decodes a [dynamic value][data] to a DeleteAssetStatus.
+  ///
+  /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
+  /// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
+  /// cannot be decoded successfully, then an [UnimplementedError] is thrown.
+  ///
+  /// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
+  /// and users are still using an old app with the old code.
+  DeleteAssetStatus? decode(dynamic data, {bool allowNull = true}) {
+    if (data != null) {
+      switch (data.toString()) {
+        case r'SUCCESS': return DeleteAssetStatus.SUCCESS;
+        case r'FAILED': return DeleteAssetStatus.FAILED;
+        default:
+          if (!allowNull) {
+            throw ArgumentError('Unknown enum value to decode: $data');
+          }
+      }
+    }
+    return null;
+  }
+
+  /// Singleton [DeleteAssetStatusTypeTransformer] instance.
+  static DeleteAssetStatusTypeTransformer? _instance;
+}
+
diff --git a/mobile/openapi/lib/model/device_info_response_dto.dart b/mobile/openapi/lib/model/device_info_response_dto.dart
new file mode 100644
index 0000000000..ad831c4691
--- /dev/null
+++ b/mobile/openapi/lib/model/device_info_response_dto.dart
@@ -0,0 +1,151 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class DeviceInfoResponseDto {
+  /// Returns a new [DeviceInfoResponseDto] instance.
+  DeviceInfoResponseDto({
+    required this.id,
+    required this.deviceType,
+    required this.userId,
+    required this.deviceId,
+    required this.createdAt,
+    required this.isAutoBackup,
+  });
+
+  int id;
+
+  DeviceTypeEnum deviceType;
+
+  String userId;
+
+  String deviceId;
+
+  String createdAt;
+
+  bool isAutoBackup;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is DeviceInfoResponseDto &&
+     other.id == id &&
+     other.deviceType == deviceType &&
+     other.userId == userId &&
+     other.deviceId == deviceId &&
+     other.createdAt == createdAt &&
+     other.isAutoBackup == isAutoBackup;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode) +
+    (deviceType.hashCode) +
+    (userId.hashCode) +
+    (deviceId.hashCode) +
+    (createdAt.hashCode) +
+    (isAutoBackup.hashCode);
+
+  @override
+  String toString() => 'DeviceInfoResponseDto[id=$id, deviceType=$deviceType, userId=$userId, deviceId=$deviceId, createdAt=$createdAt, isAutoBackup=$isAutoBackup]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+      _json[r'deviceType'] = deviceType;
+      _json[r'userId'] = userId;
+      _json[r'deviceId'] = deviceId;
+      _json[r'createdAt'] = createdAt;
+      _json[r'isAutoBackup'] = isAutoBackup;
+    return _json;
+  }
+
+  /// Returns a new [DeviceInfoResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static DeviceInfoResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "DeviceInfoResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "DeviceInfoResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return DeviceInfoResponseDto(
+        id: mapValueOfType<int>(json, r'id')!,
+        deviceType: DeviceTypeEnum.fromJson(json[r'deviceType'])!,
+        userId: mapValueOfType<String>(json, r'userId')!,
+        deviceId: mapValueOfType<String>(json, r'deviceId')!,
+        createdAt: mapValueOfType<String>(json, r'createdAt')!,
+        isAutoBackup: mapValueOfType<bool>(json, r'isAutoBackup')!,
+      );
+    }
+    return null;
+  }
+
+  static List<DeviceInfoResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <DeviceInfoResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = DeviceInfoResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, DeviceInfoResponseDto> mapFromJson(dynamic json) {
+    final map = <String, DeviceInfoResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = DeviceInfoResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of DeviceInfoResponseDto-objects as value to a dart map
+  static Map<String, List<DeviceInfoResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<DeviceInfoResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = DeviceInfoResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+    'deviceType',
+    'userId',
+    'deviceId',
+    'createdAt',
+    'isAutoBackup',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/device_type_enum.dart b/mobile/openapi/lib/model/device_type_enum.dart
new file mode 100644
index 0000000000..15f81c3d44
--- /dev/null
+++ b/mobile/openapi/lib/model/device_type_enum.dart
@@ -0,0 +1,88 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+
+class DeviceTypeEnum {
+  /// Instantiate a new enum with the provided [value].
+  const DeviceTypeEnum._(this.value);
+
+  /// The underlying value of this enum member.
+  final String value;
+
+  @override
+  String toString() => value;
+
+  String toJson() => value;
+
+  static const IOS = DeviceTypeEnum._(r'IOS');
+  static const ANDROID = DeviceTypeEnum._(r'ANDROID');
+  static const WEB = DeviceTypeEnum._(r'WEB');
+
+  /// List of all possible values in this [enum][DeviceTypeEnum].
+  static const values = <DeviceTypeEnum>[
+    IOS,
+    ANDROID,
+    WEB,
+  ];
+
+  static DeviceTypeEnum? fromJson(dynamic value) => DeviceTypeEnumTypeTransformer().decode(value);
+
+  static List<DeviceTypeEnum>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <DeviceTypeEnum>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = DeviceTypeEnum.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+}
+
+/// Transformation class that can [encode] an instance of [DeviceTypeEnum] to String,
+/// and [decode] dynamic data back to [DeviceTypeEnum].
+class DeviceTypeEnumTypeTransformer {
+  factory DeviceTypeEnumTypeTransformer() => _instance ??= const DeviceTypeEnumTypeTransformer._();
+
+  const DeviceTypeEnumTypeTransformer._();
+
+  String encode(DeviceTypeEnum data) => data.value;
+
+  /// Decodes a [dynamic value][data] to a DeviceTypeEnum.
+  ///
+  /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
+  /// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
+  /// cannot be decoded successfully, then an [UnimplementedError] is thrown.
+  ///
+  /// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
+  /// and users are still using an old app with the old code.
+  DeviceTypeEnum? decode(dynamic data, {bool allowNull = true}) {
+    if (data != null) {
+      switch (data.toString()) {
+        case r'IOS': return DeviceTypeEnum.IOS;
+        case r'ANDROID': return DeviceTypeEnum.ANDROID;
+        case r'WEB': return DeviceTypeEnum.WEB;
+        default:
+          if (!allowNull) {
+            throw ArgumentError('Unknown enum value to decode: $data');
+          }
+      }
+    }
+    return null;
+  }
+
+  /// Singleton [DeviceTypeEnumTypeTransformer] instance.
+  static DeviceTypeEnumTypeTransformer? _instance;
+}
+
diff --git a/mobile/openapi/lib/model/exif_response_dto.dart b/mobile/openapi/lib/model/exif_response_dto.dart
new file mode 100644
index 0000000000..199c955e93
--- /dev/null
+++ b/mobile/openapi/lib/model/exif_response_dto.dart
@@ -0,0 +1,341 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ExifResponseDto {
+  /// Returns a new [ExifResponseDto] instance.
+  ExifResponseDto({
+    this.id,
+    this.make,
+    this.model,
+    this.imageName,
+    this.exifImageWidth,
+    this.exifImageHeight,
+    this.fileSizeInByte,
+    this.orientation,
+    this.dateTimeOriginal,
+    this.modifyDate,
+    this.lensModel,
+    this.fNumber,
+    this.focalLength,
+    this.iso,
+    this.exposureTime,
+    this.latitude,
+    this.longitude,
+    this.city,
+    this.state,
+    this.country,
+  });
+
+  String? id;
+
+  String? make;
+
+  String? model;
+
+  String? imageName;
+
+  num? exifImageWidth;
+
+  num? exifImageHeight;
+
+  num? fileSizeInByte;
+
+  String? orientation;
+
+  DateTime? dateTimeOriginal;
+
+  DateTime? modifyDate;
+
+  String? lensModel;
+
+  num? fNumber;
+
+  num? focalLength;
+
+  num? iso;
+
+  num? exposureTime;
+
+  num? latitude;
+
+  num? longitude;
+
+  String? city;
+
+  String? state;
+
+  String? country;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto &&
+     other.id == id &&
+     other.make == make &&
+     other.model == model &&
+     other.imageName == imageName &&
+     other.exifImageWidth == exifImageWidth &&
+     other.exifImageHeight == exifImageHeight &&
+     other.fileSizeInByte == fileSizeInByte &&
+     other.orientation == orientation &&
+     other.dateTimeOriginal == dateTimeOriginal &&
+     other.modifyDate == modifyDate &&
+     other.lensModel == lensModel &&
+     other.fNumber == fNumber &&
+     other.focalLength == focalLength &&
+     other.iso == iso &&
+     other.exposureTime == exposureTime &&
+     other.latitude == latitude &&
+     other.longitude == longitude &&
+     other.city == city &&
+     other.state == state &&
+     other.country == country;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id == null ? 0 : id!.hashCode) +
+    (make == null ? 0 : make!.hashCode) +
+    (model == null ? 0 : model!.hashCode) +
+    (imageName == null ? 0 : imageName!.hashCode) +
+    (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) +
+    (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) +
+    (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) +
+    (orientation == null ? 0 : orientation!.hashCode) +
+    (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
+    (modifyDate == null ? 0 : modifyDate!.hashCode) +
+    (lensModel == null ? 0 : lensModel!.hashCode) +
+    (fNumber == null ? 0 : fNumber!.hashCode) +
+    (focalLength == null ? 0 : focalLength!.hashCode) +
+    (iso == null ? 0 : iso!.hashCode) +
+    (exposureTime == null ? 0 : exposureTime!.hashCode) +
+    (latitude == null ? 0 : latitude!.hashCode) +
+    (longitude == null ? 0 : longitude!.hashCode) +
+    (city == null ? 0 : city!.hashCode) +
+    (state == null ? 0 : state!.hashCode) +
+    (country == null ? 0 : country!.hashCode);
+
+  @override
+  String toString() => 'ExifResponseDto[id=$id, make=$make, model=$model, imageName=$imageName, exifImageWidth=$exifImageWidth, exifImageHeight=$exifImageHeight, fileSizeInByte=$fileSizeInByte, orientation=$orientation, dateTimeOriginal=$dateTimeOriginal, modifyDate=$modifyDate, lensModel=$lensModel, fNumber=$fNumber, focalLength=$focalLength, iso=$iso, exposureTime=$exposureTime, latitude=$latitude, longitude=$longitude, city=$city, state=$state, country=$country]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+    if (id != null) {
+      _json[r'id'] = id;
+    } else {
+      _json[r'id'] = null;
+    }
+    if (make != null) {
+      _json[r'make'] = make;
+    } else {
+      _json[r'make'] = null;
+    }
+    if (model != null) {
+      _json[r'model'] = model;
+    } else {
+      _json[r'model'] = null;
+    }
+    if (imageName != null) {
+      _json[r'imageName'] = imageName;
+    } else {
+      _json[r'imageName'] = null;
+    }
+    if (exifImageWidth != null) {
+      _json[r'exifImageWidth'] = exifImageWidth;
+    } else {
+      _json[r'exifImageWidth'] = null;
+    }
+    if (exifImageHeight != null) {
+      _json[r'exifImageHeight'] = exifImageHeight;
+    } else {
+      _json[r'exifImageHeight'] = null;
+    }
+    if (fileSizeInByte != null) {
+      _json[r'fileSizeInByte'] = fileSizeInByte;
+    } else {
+      _json[r'fileSizeInByte'] = null;
+    }
+    if (orientation != null) {
+      _json[r'orientation'] = orientation;
+    } else {
+      _json[r'orientation'] = null;
+    }
+    if (dateTimeOriginal != null) {
+      _json[r'dateTimeOriginal'] = dateTimeOriginal!.toUtc().toIso8601String();
+    } else {
+      _json[r'dateTimeOriginal'] = null;
+    }
+    if (modifyDate != null) {
+      _json[r'modifyDate'] = modifyDate!.toUtc().toIso8601String();
+    } else {
+      _json[r'modifyDate'] = null;
+    }
+    if (lensModel != null) {
+      _json[r'lensModel'] = lensModel;
+    } else {
+      _json[r'lensModel'] = null;
+    }
+    if (fNumber != null) {
+      _json[r'fNumber'] = fNumber;
+    } else {
+      _json[r'fNumber'] = null;
+    }
+    if (focalLength != null) {
+      _json[r'focalLength'] = focalLength;
+    } else {
+      _json[r'focalLength'] = null;
+    }
+    if (iso != null) {
+      _json[r'iso'] = iso;
+    } else {
+      _json[r'iso'] = null;
+    }
+    if (exposureTime != null) {
+      _json[r'exposureTime'] = exposureTime;
+    } else {
+      _json[r'exposureTime'] = null;
+    }
+    if (latitude != null) {
+      _json[r'latitude'] = latitude;
+    } else {
+      _json[r'latitude'] = null;
+    }
+    if (longitude != null) {
+      _json[r'longitude'] = longitude;
+    } else {
+      _json[r'longitude'] = null;
+    }
+    if (city != null) {
+      _json[r'city'] = city;
+    } else {
+      _json[r'city'] = null;
+    }
+    if (state != null) {
+      _json[r'state'] = state;
+    } else {
+      _json[r'state'] = null;
+    }
+    if (country != null) {
+      _json[r'country'] = country;
+    } else {
+      _json[r'country'] = null;
+    }
+    return _json;
+  }
+
+  /// Returns a new [ExifResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static ExifResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "ExifResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "ExifResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return ExifResponseDto(
+        id: mapValueOfType<String>(json, r'id'),
+        make: mapValueOfType<String>(json, r'make'),
+        model: mapValueOfType<String>(json, r'model'),
+        imageName: mapValueOfType<String>(json, r'imageName'),
+        exifImageWidth: json[r'exifImageWidth'] == null
+            ? null
+            : num.parse(json[r'exifImageWidth'].toString()),
+        exifImageHeight: json[r'exifImageHeight'] == null
+            ? null
+            : num.parse(json[r'exifImageHeight'].toString()),
+        fileSizeInByte: json[r'fileSizeInByte'] == null
+            ? null
+            : num.parse(json[r'fileSizeInByte'].toString()),
+        orientation: mapValueOfType<String>(json, r'orientation'),
+        dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', ''),
+        modifyDate: mapDateTime(json, r'modifyDate', ''),
+        lensModel: mapValueOfType<String>(json, r'lensModel'),
+        fNumber: json[r'fNumber'] == null
+            ? null
+            : num.parse(json[r'fNumber'].toString()),
+        focalLength: json[r'focalLength'] == null
+            ? null
+            : num.parse(json[r'focalLength'].toString()),
+        iso: json[r'iso'] == null
+            ? null
+            : num.parse(json[r'iso'].toString()),
+        exposureTime: json[r'exposureTime'] == null
+            ? null
+            : num.parse(json[r'exposureTime'].toString()),
+        latitude: json[r'latitude'] == null
+            ? null
+            : num.parse(json[r'latitude'].toString()),
+        longitude: json[r'longitude'] == null
+            ? null
+            : num.parse(json[r'longitude'].toString()),
+        city: mapValueOfType<String>(json, r'city'),
+        state: mapValueOfType<String>(json, r'state'),
+        country: mapValueOfType<String>(json, r'country'),
+      );
+    }
+    return null;
+  }
+
+  static List<ExifResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <ExifResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = ExifResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, ExifResponseDto> mapFromJson(dynamic json) {
+    final map = <String, ExifResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ExifResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of ExifResponseDto-objects as value to a dart map
+  static Map<String, List<ExifResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<ExifResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ExifResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+  };
+}
+
diff --git a/mobile/openapi/lib/model/login_credential_dto.dart b/mobile/openapi/lib/model/login_credential_dto.dart
new file mode 100644
index 0000000000..b8a23be593
--- /dev/null
+++ b/mobile/openapi/lib/model/login_credential_dto.dart
@@ -0,0 +1,119 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class LoginCredentialDto {
+  /// Returns a new [LoginCredentialDto] instance.
+  LoginCredentialDto({
+    required this.email,
+    required this.password,
+  });
+
+  String email;
+
+  String password;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is LoginCredentialDto &&
+     other.email == email &&
+     other.password == password;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (email.hashCode) +
+    (password.hashCode);
+
+  @override
+  String toString() => 'LoginCredentialDto[email=$email, password=$password]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'email'] = email;
+      _json[r'password'] = password;
+    return _json;
+  }
+
+  /// Returns a new [LoginCredentialDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static LoginCredentialDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "LoginCredentialDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "LoginCredentialDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return LoginCredentialDto(
+        email: mapValueOfType<String>(json, r'email')!,
+        password: mapValueOfType<String>(json, r'password')!,
+      );
+    }
+    return null;
+  }
+
+  static List<LoginCredentialDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <LoginCredentialDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = LoginCredentialDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, LoginCredentialDto> mapFromJson(dynamic json) {
+    final map = <String, LoginCredentialDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = LoginCredentialDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of LoginCredentialDto-objects as value to a dart map
+  static Map<String, List<LoginCredentialDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<LoginCredentialDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = LoginCredentialDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'email',
+    'password',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/login_response_dto.dart b/mobile/openapi/lib/model/login_response_dto.dart
new file mode 100644
index 0000000000..4eec0475aa
--- /dev/null
+++ b/mobile/openapi/lib/model/login_response_dto.dart
@@ -0,0 +1,167 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class LoginResponseDto {
+  /// Returns a new [LoginResponseDto] instance.
+  LoginResponseDto({
+    required this.accessToken,
+    required this.userId,
+    required this.userEmail,
+    required this.firstName,
+    required this.lastName,
+    required this.profileImagePath,
+    required this.isAdmin,
+    required this.shouldChangePassword,
+  });
+
+  String accessToken;
+
+  String userId;
+
+  String userEmail;
+
+  String firstName;
+
+  String lastName;
+
+  String profileImagePath;
+
+  bool isAdmin;
+
+  bool shouldChangePassword;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is LoginResponseDto &&
+     other.accessToken == accessToken &&
+     other.userId == userId &&
+     other.userEmail == userEmail &&
+     other.firstName == firstName &&
+     other.lastName == lastName &&
+     other.profileImagePath == profileImagePath &&
+     other.isAdmin == isAdmin &&
+     other.shouldChangePassword == shouldChangePassword;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (accessToken.hashCode) +
+    (userId.hashCode) +
+    (userEmail.hashCode) +
+    (firstName.hashCode) +
+    (lastName.hashCode) +
+    (profileImagePath.hashCode) +
+    (isAdmin.hashCode) +
+    (shouldChangePassword.hashCode);
+
+  @override
+  String toString() => 'LoginResponseDto[accessToken=$accessToken, userId=$userId, userEmail=$userEmail, firstName=$firstName, lastName=$lastName, profileImagePath=$profileImagePath, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'accessToken'] = accessToken;
+      _json[r'userId'] = userId;
+      _json[r'userEmail'] = userEmail;
+      _json[r'firstName'] = firstName;
+      _json[r'lastName'] = lastName;
+      _json[r'profileImagePath'] = profileImagePath;
+      _json[r'isAdmin'] = isAdmin;
+      _json[r'shouldChangePassword'] = shouldChangePassword;
+    return _json;
+  }
+
+  /// Returns a new [LoginResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static LoginResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "LoginResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "LoginResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return LoginResponseDto(
+        accessToken: mapValueOfType<String>(json, r'accessToken')!,
+        userId: mapValueOfType<String>(json, r'userId')!,
+        userEmail: mapValueOfType<String>(json, r'userEmail')!,
+        firstName: mapValueOfType<String>(json, r'firstName')!,
+        lastName: mapValueOfType<String>(json, r'lastName')!,
+        profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
+        isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
+        shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
+      );
+    }
+    return null;
+  }
+
+  static List<LoginResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <LoginResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = LoginResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, LoginResponseDto> mapFromJson(dynamic json) {
+    final map = <String, LoginResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = LoginResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of LoginResponseDto-objects as value to a dart map
+  static Map<String, List<LoginResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<LoginResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = LoginResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'accessToken',
+    'userId',
+    'userEmail',
+    'firstName',
+    'lastName',
+    'profileImagePath',
+    'isAdmin',
+    'shouldChangePassword',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/remove_assets_dto.dart b/mobile/openapi/lib/model/remove_assets_dto.dart
new file mode 100644
index 0000000000..1457bac388
--- /dev/null
+++ b/mobile/openapi/lib/model/remove_assets_dto.dart
@@ -0,0 +1,113 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class RemoveAssetsDto {
+  /// Returns a new [RemoveAssetsDto] instance.
+  RemoveAssetsDto({
+    this.assetIds = const [],
+  });
+
+  List<String> assetIds;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is RemoveAssetsDto &&
+     other.assetIds == assetIds;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (assetIds.hashCode);
+
+  @override
+  String toString() => 'RemoveAssetsDto[assetIds=$assetIds]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'assetIds'] = assetIds;
+    return _json;
+  }
+
+  /// Returns a new [RemoveAssetsDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static RemoveAssetsDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "RemoveAssetsDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "RemoveAssetsDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return RemoveAssetsDto(
+        assetIds: json[r'assetIds'] is List
+            ? (json[r'assetIds'] as List).cast<String>()
+            : const [],
+      );
+    }
+    return null;
+  }
+
+  static List<RemoveAssetsDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <RemoveAssetsDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = RemoveAssetsDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, RemoveAssetsDto> mapFromJson(dynamic json) {
+    final map = <String, RemoveAssetsDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = RemoveAssetsDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of RemoveAssetsDto-objects as value to a dart map
+  static Map<String, List<RemoveAssetsDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<RemoveAssetsDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = RemoveAssetsDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'assetIds',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/search_asset_dto.dart b/mobile/openapi/lib/model/search_asset_dto.dart
new file mode 100644
index 0000000000..d90682750b
--- /dev/null
+++ b/mobile/openapi/lib/model/search_asset_dto.dart
@@ -0,0 +1,111 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class SearchAssetDto {
+  /// Returns a new [SearchAssetDto] instance.
+  SearchAssetDto({
+    required this.searchTerm,
+  });
+
+  String searchTerm;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is SearchAssetDto &&
+     other.searchTerm == searchTerm;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (searchTerm.hashCode);
+
+  @override
+  String toString() => 'SearchAssetDto[searchTerm=$searchTerm]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'searchTerm'] = searchTerm;
+    return _json;
+  }
+
+  /// Returns a new [SearchAssetDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static SearchAssetDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "SearchAssetDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "SearchAssetDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return SearchAssetDto(
+        searchTerm: mapValueOfType<String>(json, r'searchTerm')!,
+      );
+    }
+    return null;
+  }
+
+  static List<SearchAssetDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <SearchAssetDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = SearchAssetDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, SearchAssetDto> mapFromJson(dynamic json) {
+    final map = <String, SearchAssetDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = SearchAssetDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of SearchAssetDto-objects as value to a dart map
+  static Map<String, List<SearchAssetDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<SearchAssetDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = SearchAssetDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'searchTerm',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/server_info_response_dto.dart b/mobile/openapi/lib/model/server_info_response_dto.dart
new file mode 100644
index 0000000000..0bc23439ca
--- /dev/null
+++ b/mobile/openapi/lib/model/server_info_response_dto.dart
@@ -0,0 +1,159 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ServerInfoResponseDto {
+  /// Returns a new [ServerInfoResponseDto] instance.
+  ServerInfoResponseDto({
+    required this.diskSizeRaw,
+    required this.diskUseRaw,
+    required this.diskAvailableRaw,
+    required this.diskUsagePercentage,
+    required this.diskSize,
+    required this.diskUse,
+    required this.diskAvailable,
+  });
+
+  int diskSizeRaw;
+
+  int diskUseRaw;
+
+  int diskAvailableRaw;
+
+  double diskUsagePercentage;
+
+  String diskSize;
+
+  String diskUse;
+
+  String diskAvailable;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is ServerInfoResponseDto &&
+     other.diskSizeRaw == diskSizeRaw &&
+     other.diskUseRaw == diskUseRaw &&
+     other.diskAvailableRaw == diskAvailableRaw &&
+     other.diskUsagePercentage == diskUsagePercentage &&
+     other.diskSize == diskSize &&
+     other.diskUse == diskUse &&
+     other.diskAvailable == diskAvailable;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (diskSizeRaw.hashCode) +
+    (diskUseRaw.hashCode) +
+    (diskAvailableRaw.hashCode) +
+    (diskUsagePercentage.hashCode) +
+    (diskSize.hashCode) +
+    (diskUse.hashCode) +
+    (diskAvailable.hashCode);
+
+  @override
+  String toString() => 'ServerInfoResponseDto[diskSizeRaw=$diskSizeRaw, diskUseRaw=$diskUseRaw, diskAvailableRaw=$diskAvailableRaw, diskUsagePercentage=$diskUsagePercentage, diskSize=$diskSize, diskUse=$diskUse, diskAvailable=$diskAvailable]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'diskSizeRaw'] = diskSizeRaw;
+      _json[r'diskUseRaw'] = diskUseRaw;
+      _json[r'diskAvailableRaw'] = diskAvailableRaw;
+      _json[r'diskUsagePercentage'] = diskUsagePercentage;
+      _json[r'diskSize'] = diskSize;
+      _json[r'diskUse'] = diskUse;
+      _json[r'diskAvailable'] = diskAvailable;
+    return _json;
+  }
+
+  /// Returns a new [ServerInfoResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static ServerInfoResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "ServerInfoResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "ServerInfoResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return ServerInfoResponseDto(
+        diskSizeRaw: mapValueOfType<int>(json, r'diskSizeRaw')!,
+        diskUseRaw: mapValueOfType<int>(json, r'diskUseRaw')!,
+        diskAvailableRaw: mapValueOfType<int>(json, r'diskAvailableRaw')!,
+        diskUsagePercentage: mapValueOfType<double>(json, r'diskUsagePercentage')!,
+        diskSize: mapValueOfType<String>(json, r'diskSize')!,
+        diskUse: mapValueOfType<String>(json, r'diskUse')!,
+        diskAvailable: mapValueOfType<String>(json, r'diskAvailable')!,
+      );
+    }
+    return null;
+  }
+
+  static List<ServerInfoResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <ServerInfoResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = ServerInfoResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, ServerInfoResponseDto> mapFromJson(dynamic json) {
+    final map = <String, ServerInfoResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ServerInfoResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of ServerInfoResponseDto-objects as value to a dart map
+  static Map<String, List<ServerInfoResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<ServerInfoResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ServerInfoResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'diskSizeRaw',
+    'diskUseRaw',
+    'diskAvailableRaw',
+    'diskUsagePercentage',
+    'diskSize',
+    'diskUse',
+    'diskAvailable',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/server_ping_response.dart b/mobile/openapi/lib/model/server_ping_response.dart
new file mode 100644
index 0000000000..56af4fa5ba
--- /dev/null
+++ b/mobile/openapi/lib/model/server_ping_response.dart
@@ -0,0 +1,111 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ServerPingResponse {
+  /// Returns a new [ServerPingResponse] instance.
+  ServerPingResponse({
+    required this.res,
+  });
+
+  String res;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is ServerPingResponse &&
+     other.res == res;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (res.hashCode);
+
+  @override
+  String toString() => 'ServerPingResponse[res=$res]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'res'] = res;
+    return _json;
+  }
+
+  /// Returns a new [ServerPingResponse] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static ServerPingResponse? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "ServerPingResponse[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "ServerPingResponse[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return ServerPingResponse(
+        res: mapValueOfType<String>(json, r'res')!,
+      );
+    }
+    return null;
+  }
+
+  static List<ServerPingResponse>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <ServerPingResponse>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = ServerPingResponse.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, ServerPingResponse> mapFromJson(dynamic json) {
+    final map = <String, ServerPingResponse>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ServerPingResponse.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of ServerPingResponse-objects as value to a dart map
+  static Map<String, List<ServerPingResponse>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<ServerPingResponse>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ServerPingResponse.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'res',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/server_version_reponse_dto.dart b/mobile/openapi/lib/model/server_version_reponse_dto.dart
new file mode 100644
index 0000000000..72caaf1940
--- /dev/null
+++ b/mobile/openapi/lib/model/server_version_reponse_dto.dart
@@ -0,0 +1,135 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ServerVersionReponseDto {
+  /// Returns a new [ServerVersionReponseDto] instance.
+  ServerVersionReponseDto({
+    required this.major,
+    required this.minor,
+    required this.patch_,
+    required this.build,
+  });
+
+  int major;
+
+  int minor;
+
+  int patch_;
+
+  int build;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is ServerVersionReponseDto &&
+     other.major == major &&
+     other.minor == minor &&
+     other.patch_ == patch_ &&
+     other.build == build;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (major.hashCode) +
+    (minor.hashCode) +
+    (patch_.hashCode) +
+    (build.hashCode);
+
+  @override
+  String toString() => 'ServerVersionReponseDto[major=$major, minor=$minor, patch_=$patch_, build=$build]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'major'] = major;
+      _json[r'minor'] = minor;
+      _json[r'patch'] = patch_;
+      _json[r'build'] = build;
+    return _json;
+  }
+
+  /// Returns a new [ServerVersionReponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static ServerVersionReponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "ServerVersionReponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "ServerVersionReponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return ServerVersionReponseDto(
+        major: mapValueOfType<int>(json, r'major')!,
+        minor: mapValueOfType<int>(json, r'minor')!,
+        patch_: mapValueOfType<int>(json, r'patch')!,
+        build: mapValueOfType<int>(json, r'build')!,
+      );
+    }
+    return null;
+  }
+
+  static List<ServerVersionReponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <ServerVersionReponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = ServerVersionReponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, ServerVersionReponseDto> mapFromJson(dynamic json) {
+    final map = <String, ServerVersionReponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ServerVersionReponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of ServerVersionReponseDto-objects as value to a dart map
+  static Map<String, List<ServerVersionReponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<ServerVersionReponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ServerVersionReponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'major',
+    'minor',
+    'patch',
+    'build',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/sign_up_dto.dart b/mobile/openapi/lib/model/sign_up_dto.dart
new file mode 100644
index 0000000000..dc027a3ce0
--- /dev/null
+++ b/mobile/openapi/lib/model/sign_up_dto.dart
@@ -0,0 +1,135 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class SignUpDto {
+  /// Returns a new [SignUpDto] instance.
+  SignUpDto({
+    required this.email,
+    required this.password,
+    required this.firstName,
+    required this.lastName,
+  });
+
+  String email;
+
+  String password;
+
+  String firstName;
+
+  String lastName;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is SignUpDto &&
+     other.email == email &&
+     other.password == password &&
+     other.firstName == firstName &&
+     other.lastName == lastName;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (email.hashCode) +
+    (password.hashCode) +
+    (firstName.hashCode) +
+    (lastName.hashCode);
+
+  @override
+  String toString() => 'SignUpDto[email=$email, password=$password, firstName=$firstName, lastName=$lastName]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'email'] = email;
+      _json[r'password'] = password;
+      _json[r'firstName'] = firstName;
+      _json[r'lastName'] = lastName;
+    return _json;
+  }
+
+  /// Returns a new [SignUpDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static SignUpDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "SignUpDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "SignUpDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return SignUpDto(
+        email: mapValueOfType<String>(json, r'email')!,
+        password: mapValueOfType<String>(json, r'password')!,
+        firstName: mapValueOfType<String>(json, r'firstName')!,
+        lastName: mapValueOfType<String>(json, r'lastName')!,
+      );
+    }
+    return null;
+  }
+
+  static List<SignUpDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <SignUpDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = SignUpDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, SignUpDto> mapFromJson(dynamic json) {
+    final map = <String, SignUpDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = SignUpDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of SignUpDto-objects as value to a dart map
+  static Map<String, List<SignUpDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<SignUpDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = SignUpDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'email',
+    'password',
+    'firstName',
+    'lastName',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/smart_info_response_dto.dart b/mobile/openapi/lib/model/smart_info_response_dto.dart
new file mode 100644
index 0000000000..e291cf13e3
--- /dev/null
+++ b/mobile/openapi/lib/model/smart_info_response_dto.dart
@@ -0,0 +1,146 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class SmartInfoResponseDto {
+  /// Returns a new [SmartInfoResponseDto] instance.
+  SmartInfoResponseDto({
+    this.id,
+    this.tags = const [],
+    this.objects = const [],
+  });
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  String? id;
+
+  List<String>? tags;
+
+  List<String>? objects;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is SmartInfoResponseDto &&
+     other.id == id &&
+     other.tags == tags &&
+     other.objects == objects;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id == null ? 0 : id!.hashCode) +
+    (tags == null ? 0 : tags!.hashCode) +
+    (objects == null ? 0 : objects!.hashCode);
+
+  @override
+  String toString() => 'SmartInfoResponseDto[id=$id, tags=$tags, objects=$objects]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+    if (id != null) {
+      _json[r'id'] = id;
+    } else {
+      _json[r'id'] = null;
+    }
+    if (tags != null) {
+      _json[r'tags'] = tags;
+    } else {
+      _json[r'tags'] = null;
+    }
+    if (objects != null) {
+      _json[r'objects'] = objects;
+    } else {
+      _json[r'objects'] = null;
+    }
+    return _json;
+  }
+
+  /// Returns a new [SmartInfoResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static SmartInfoResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "SmartInfoResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "SmartInfoResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return SmartInfoResponseDto(
+        id: mapValueOfType<String>(json, r'id'),
+        tags: json[r'tags'] is List
+            ? (json[r'tags'] as List).cast<String>()
+            : const [],
+        objects: json[r'objects'] is List
+            ? (json[r'objects'] as List).cast<String>()
+            : const [],
+      );
+    }
+    return null;
+  }
+
+  static List<SmartInfoResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <SmartInfoResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = SmartInfoResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, SmartInfoResponseDto> mapFromJson(dynamic json) {
+    final map = <String, SmartInfoResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = SmartInfoResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of SmartInfoResponseDto-objects as value to a dart map
+  static Map<String, List<SmartInfoResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<SmartInfoResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = SmartInfoResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+  };
+}
+
diff --git a/mobile/openapi/lib/model/update_album_dto.dart b/mobile/openapi/lib/model/update_album_dto.dart
new file mode 100644
index 0000000000..74f680b185
--- /dev/null
+++ b/mobile/openapi/lib/model/update_album_dto.dart
@@ -0,0 +1,119 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class UpdateAlbumDto {
+  /// Returns a new [UpdateAlbumDto] instance.
+  UpdateAlbumDto({
+    required this.albumName,
+    required this.ownerId,
+  });
+
+  String albumName;
+
+  String ownerId;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumDto &&
+     other.albumName == albumName &&
+     other.ownerId == ownerId;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (albumName.hashCode) +
+    (ownerId.hashCode);
+
+  @override
+  String toString() => 'UpdateAlbumDto[albumName=$albumName, ownerId=$ownerId]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'albumName'] = albumName;
+      _json[r'ownerId'] = ownerId;
+    return _json;
+  }
+
+  /// Returns a new [UpdateAlbumDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static UpdateAlbumDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "UpdateAlbumDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "UpdateAlbumDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return UpdateAlbumDto(
+        albumName: mapValueOfType<String>(json, r'albumName')!,
+        ownerId: mapValueOfType<String>(json, r'ownerId')!,
+      );
+    }
+    return null;
+  }
+
+  static List<UpdateAlbumDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <UpdateAlbumDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = UpdateAlbumDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, UpdateAlbumDto> mapFromJson(dynamic json) {
+    final map = <String, UpdateAlbumDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UpdateAlbumDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of UpdateAlbumDto-objects as value to a dart map
+  static Map<String, List<UpdateAlbumDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<UpdateAlbumDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UpdateAlbumDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'albumName',
+    'ownerId',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/update_device_info_dto.dart b/mobile/openapi/lib/model/update_device_info_dto.dart
new file mode 100644
index 0000000000..c11a2df567
--- /dev/null
+++ b/mobile/openapi/lib/model/update_device_info_dto.dart
@@ -0,0 +1,136 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class UpdateDeviceInfoDto {
+  /// Returns a new [UpdateDeviceInfoDto] instance.
+  UpdateDeviceInfoDto({
+    required this.deviceType,
+    required this.deviceId,
+    this.isAutoBackup,
+  });
+
+  DeviceTypeEnum deviceType;
+
+  String deviceId;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  bool? isAutoBackup;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is UpdateDeviceInfoDto &&
+     other.deviceType == deviceType &&
+     other.deviceId == deviceId &&
+     other.isAutoBackup == isAutoBackup;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (deviceType.hashCode) +
+    (deviceId.hashCode) +
+    (isAutoBackup == null ? 0 : isAutoBackup!.hashCode);
+
+  @override
+  String toString() => 'UpdateDeviceInfoDto[deviceType=$deviceType, deviceId=$deviceId, isAutoBackup=$isAutoBackup]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'deviceType'] = deviceType;
+      _json[r'deviceId'] = deviceId;
+    if (isAutoBackup != null) {
+      _json[r'isAutoBackup'] = isAutoBackup;
+    } else {
+      _json[r'isAutoBackup'] = null;
+    }
+    return _json;
+  }
+
+  /// Returns a new [UpdateDeviceInfoDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static UpdateDeviceInfoDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "UpdateDeviceInfoDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "UpdateDeviceInfoDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return UpdateDeviceInfoDto(
+        deviceType: DeviceTypeEnum.fromJson(json[r'deviceType'])!,
+        deviceId: mapValueOfType<String>(json, r'deviceId')!,
+        isAutoBackup: mapValueOfType<bool>(json, r'isAutoBackup'),
+      );
+    }
+    return null;
+  }
+
+  static List<UpdateDeviceInfoDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <UpdateDeviceInfoDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = UpdateDeviceInfoDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, UpdateDeviceInfoDto> mapFromJson(dynamic json) {
+    final map = <String, UpdateDeviceInfoDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UpdateDeviceInfoDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of UpdateDeviceInfoDto-objects as value to a dart map
+  static Map<String, List<UpdateDeviceInfoDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<UpdateDeviceInfoDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UpdateDeviceInfoDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'deviceType',
+    'deviceId',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/update_user_dto.dart b/mobile/openapi/lib/model/update_user_dto.dart
new file mode 100644
index 0000000000..d91de3aa04
--- /dev/null
+++ b/mobile/openapi/lib/model/update_user_dto.dart
@@ -0,0 +1,213 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class UpdateUserDto {
+  /// Returns a new [UpdateUserDto] instance.
+  UpdateUserDto({
+    required this.id,
+    this.password,
+    this.firstName,
+    this.lastName,
+    this.isAdmin,
+    this.shouldChangePassword,
+    this.profileImagePath,
+  });
+
+  String id;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  String? password;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  String? firstName;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  String? lastName;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  bool? isAdmin;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  bool? shouldChangePassword;
+
+  ///
+  /// Please note: This property should have been non-nullable! Since the specification file
+  /// does not include a default value (using the "default:" property), however, the generated
+  /// source code must fall back to having a nullable type.
+  /// Consider adding a "default:" property in the specification file to hide this note.
+  ///
+  String? profileImagePath;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is UpdateUserDto &&
+     other.id == id &&
+     other.password == password &&
+     other.firstName == firstName &&
+     other.lastName == lastName &&
+     other.isAdmin == isAdmin &&
+     other.shouldChangePassword == shouldChangePassword &&
+     other.profileImagePath == profileImagePath;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode) +
+    (password == null ? 0 : password!.hashCode) +
+    (firstName == null ? 0 : firstName!.hashCode) +
+    (lastName == null ? 0 : lastName!.hashCode) +
+    (isAdmin == null ? 0 : isAdmin!.hashCode) +
+    (shouldChangePassword == null ? 0 : shouldChangePassword!.hashCode) +
+    (profileImagePath == null ? 0 : profileImagePath!.hashCode);
+
+  @override
+  String toString() => 'UpdateUserDto[id=$id, password=$password, firstName=$firstName, lastName=$lastName, isAdmin=$isAdmin, shouldChangePassword=$shouldChangePassword, profileImagePath=$profileImagePath]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+    if (password != null) {
+      _json[r'password'] = password;
+    } else {
+      _json[r'password'] = null;
+    }
+    if (firstName != null) {
+      _json[r'firstName'] = firstName;
+    } else {
+      _json[r'firstName'] = null;
+    }
+    if (lastName != null) {
+      _json[r'lastName'] = lastName;
+    } else {
+      _json[r'lastName'] = null;
+    }
+    if (isAdmin != null) {
+      _json[r'isAdmin'] = isAdmin;
+    } else {
+      _json[r'isAdmin'] = null;
+    }
+    if (shouldChangePassword != null) {
+      _json[r'shouldChangePassword'] = shouldChangePassword;
+    } else {
+      _json[r'shouldChangePassword'] = null;
+    }
+    if (profileImagePath != null) {
+      _json[r'profileImagePath'] = profileImagePath;
+    } else {
+      _json[r'profileImagePath'] = null;
+    }
+    return _json;
+  }
+
+  /// Returns a new [UpdateUserDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static UpdateUserDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "UpdateUserDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "UpdateUserDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return UpdateUserDto(
+        id: mapValueOfType<String>(json, r'id')!,
+        password: mapValueOfType<String>(json, r'password'),
+        firstName: mapValueOfType<String>(json, r'firstName'),
+        lastName: mapValueOfType<String>(json, r'lastName'),
+        isAdmin: mapValueOfType<bool>(json, r'isAdmin'),
+        shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword'),
+        profileImagePath: mapValueOfType<String>(json, r'profileImagePath'),
+      );
+    }
+    return null;
+  }
+
+  static List<UpdateUserDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <UpdateUserDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = UpdateUserDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, UpdateUserDto> mapFromJson(dynamic json) {
+    final map = <String, UpdateUserDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UpdateUserDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of UpdateUserDto-objects as value to a dart map
+  static Map<String, List<UpdateUserDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<UpdateUserDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UpdateUserDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/user_count_response_dto.dart b/mobile/openapi/lib/model/user_count_response_dto.dart
new file mode 100644
index 0000000000..ba11a4c362
--- /dev/null
+++ b/mobile/openapi/lib/model/user_count_response_dto.dart
@@ -0,0 +1,111 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class UserCountResponseDto {
+  /// Returns a new [UserCountResponseDto] instance.
+  UserCountResponseDto({
+    required this.userCount,
+  });
+
+  int userCount;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is UserCountResponseDto &&
+     other.userCount == userCount;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (userCount.hashCode);
+
+  @override
+  String toString() => 'UserCountResponseDto[userCount=$userCount]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'userCount'] = userCount;
+    return _json;
+  }
+
+  /// Returns a new [UserCountResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static UserCountResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "UserCountResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "UserCountResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return UserCountResponseDto(
+        userCount: mapValueOfType<int>(json, r'userCount')!,
+      );
+    }
+    return null;
+  }
+
+  static List<UserCountResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <UserCountResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = UserCountResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, UserCountResponseDto> mapFromJson(dynamic json) {
+    final map = <String, UserCountResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UserCountResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of UserCountResponseDto-objects as value to a dart map
+  static Map<String, List<UserCountResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<UserCountResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UserCountResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'userCount',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/user_response_dto.dart b/mobile/openapi/lib/model/user_response_dto.dart
new file mode 100644
index 0000000000..740808ce25
--- /dev/null
+++ b/mobile/openapi/lib/model/user_response_dto.dart
@@ -0,0 +1,167 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class UserResponseDto {
+  /// Returns a new [UserResponseDto] instance.
+  UserResponseDto({
+    required this.id,
+    required this.email,
+    required this.firstName,
+    required this.lastName,
+    required this.createdAt,
+    required this.profileImagePath,
+    required this.shouldChangePassword,
+    required this.isAdmin,
+  });
+
+  String id;
+
+  String email;
+
+  String firstName;
+
+  String lastName;
+
+  String createdAt;
+
+  String profileImagePath;
+
+  bool shouldChangePassword;
+
+  bool isAdmin;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is UserResponseDto &&
+     other.id == id &&
+     other.email == email &&
+     other.firstName == firstName &&
+     other.lastName == lastName &&
+     other.createdAt == createdAt &&
+     other.profileImagePath == profileImagePath &&
+     other.shouldChangePassword == shouldChangePassword &&
+     other.isAdmin == isAdmin;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (id.hashCode) +
+    (email.hashCode) +
+    (firstName.hashCode) +
+    (lastName.hashCode) +
+    (createdAt.hashCode) +
+    (profileImagePath.hashCode) +
+    (shouldChangePassword.hashCode) +
+    (isAdmin.hashCode);
+
+  @override
+  String toString() => 'UserResponseDto[id=$id, email=$email, firstName=$firstName, lastName=$lastName, createdAt=$createdAt, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, isAdmin=$isAdmin]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'id'] = id;
+      _json[r'email'] = email;
+      _json[r'firstName'] = firstName;
+      _json[r'lastName'] = lastName;
+      _json[r'createdAt'] = createdAt;
+      _json[r'profileImagePath'] = profileImagePath;
+      _json[r'shouldChangePassword'] = shouldChangePassword;
+      _json[r'isAdmin'] = isAdmin;
+    return _json;
+  }
+
+  /// Returns a new [UserResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static UserResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "UserResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "UserResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return UserResponseDto(
+        id: mapValueOfType<String>(json, r'id')!,
+        email: mapValueOfType<String>(json, r'email')!,
+        firstName: mapValueOfType<String>(json, r'firstName')!,
+        lastName: mapValueOfType<String>(json, r'lastName')!,
+        createdAt: mapValueOfType<String>(json, r'createdAt')!,
+        profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
+        shouldChangePassword: mapValueOfType<bool>(json, r'shouldChangePassword')!,
+        isAdmin: mapValueOfType<bool>(json, r'isAdmin')!,
+      );
+    }
+    return null;
+  }
+
+  static List<UserResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <UserResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = UserResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, UserResponseDto> mapFromJson(dynamic json) {
+    final map = <String, UserResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UserResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of UserResponseDto-objects as value to a dart map
+  static Map<String, List<UserResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<UserResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = UserResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'id',
+    'email',
+    'firstName',
+    'lastName',
+    'createdAt',
+    'profileImagePath',
+    'shouldChangePassword',
+    'isAdmin',
+  };
+}
+
diff --git a/mobile/openapi/lib/model/validate_access_token_response_dto.dart b/mobile/openapi/lib/model/validate_access_token_response_dto.dart
new file mode 100644
index 0000000000..b3f9f97359
--- /dev/null
+++ b/mobile/openapi/lib/model/validate_access_token_response_dto.dart
@@ -0,0 +1,111 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+part of openapi.api;
+
+class ValidateAccessTokenResponseDto {
+  /// Returns a new [ValidateAccessTokenResponseDto] instance.
+  ValidateAccessTokenResponseDto({
+    required this.authStatus,
+  });
+
+  bool authStatus;
+
+  @override
+  bool operator ==(Object other) => identical(this, other) || other is ValidateAccessTokenResponseDto &&
+     other.authStatus == authStatus;
+
+  @override
+  int get hashCode =>
+    // ignore: unnecessary_parenthesis
+    (authStatus.hashCode);
+
+  @override
+  String toString() => 'ValidateAccessTokenResponseDto[authStatus=$authStatus]';
+
+  Map<String, dynamic> toJson() {
+    final _json = <String, dynamic>{};
+      _json[r'authStatus'] = authStatus;
+    return _json;
+  }
+
+  /// Returns a new [ValidateAccessTokenResponseDto] instance and imports its values from
+  /// [value] if it's a [Map], null otherwise.
+  // ignore: prefer_constructors_over_static_methods
+  static ValidateAccessTokenResponseDto? fromJson(dynamic value) {
+    if (value is Map) {
+      final json = value.cast<String, dynamic>();
+
+      // Ensure that the map contains the required keys.
+      // Note 1: the values aren't checked for validity beyond being non-null.
+      // Note 2: this code is stripped in release mode!
+      assert(() {
+        requiredKeys.forEach((key) {
+          assert(json.containsKey(key), 'Required key "ValidateAccessTokenResponseDto[$key]" is missing from JSON.');
+          assert(json[key] != null, 'Required key "ValidateAccessTokenResponseDto[$key]" has a null value in JSON.');
+        });
+        return true;
+      }());
+
+      return ValidateAccessTokenResponseDto(
+        authStatus: mapValueOfType<bool>(json, r'authStatus')!,
+      );
+    }
+    return null;
+  }
+
+  static List<ValidateAccessTokenResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
+    final result = <ValidateAccessTokenResponseDto>[];
+    if (json is List && json.isNotEmpty) {
+      for (final row in json) {
+        final value = ValidateAccessTokenResponseDto.fromJson(row);
+        if (value != null) {
+          result.add(value);
+        }
+      }
+    }
+    return result.toList(growable: growable);
+  }
+
+  static Map<String, ValidateAccessTokenResponseDto> mapFromJson(dynamic json) {
+    final map = <String, ValidateAccessTokenResponseDto>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ValidateAccessTokenResponseDto.fromJson(entry.value);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  // maps a json object with a list of ValidateAccessTokenResponseDto-objects as value to a dart map
+  static Map<String, List<ValidateAccessTokenResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
+    final map = <String, List<ValidateAccessTokenResponseDto>>{};
+    if (json is Map && json.isNotEmpty) {
+      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
+      for (final entry in json.entries) {
+        final value = ValidateAccessTokenResponseDto.listFromJson(entry.value, growable: growable,);
+        if (value != null) {
+          map[entry.key] = value;
+        }
+      }
+    }
+    return map;
+  }
+
+  /// The list of required keys that must be present in a JSON.
+  static const requiredKeys = <String>{
+    'authStatus',
+  };
+}
+
diff --git a/mobile/openapi/pubspec.lock b/mobile/openapi/pubspec.lock
new file mode 100644
index 0000000000..e4c9e2004b
--- /dev/null
+++ b/mobile/openapi/pubspec.lock
@@ -0,0 +1,355 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "31.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.8.0"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.3.1"
+  async:
+    dependency: transitive
+    description:
+      name: async
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.9.0"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  cli_util:
+    dependency: transitive
+    description:
+      name: cli_util
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.3.5"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.16.0"
+  convert:
+    dependency: transitive
+    description:
+      name: convert
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.2"
+  coverage:
+    dependency: transitive
+    description:
+      name: coverage
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.3"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.2"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.1.2"
+  frontend_server_client:
+    dependency: transitive
+    description:
+      name: frontend_server_client
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  http:
+    dependency: "direct main"
+    description:
+      name: http
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.13.4"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.2.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.0.1"
+  intl:
+    dependency: "direct main"
+    description:
+      name: intl
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.17.0"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.3"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.6.4"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.2"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.12.11"
+  meta:
+    dependency: "direct main"
+    description:
+      name: meta
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.8.0"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.2"
+  node_preamble:
+    dependency: transitive
+    description:
+      name: node_preamble
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.8.2"
+  pedantic:
+    dependency: transitive
+    description:
+      name: pedantic
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.11.1"
+  pool:
+    dependency: transitive
+    description:
+      name: pool
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.5.1"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.1"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.3.1"
+  shelf_packages_handler:
+    dependency: transitive
+    description:
+      name: shelf_packages_handler
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
+  shelf_static:
+    dependency: transitive
+    description:
+      name: shelf_static
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.2"
+  source_map_stack_trace:
+    dependency: transitive
+    description:
+      name: source_map_stack_trace
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  source_maps:
+    dependency: transitive
+    description:
+      name: source_maps
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.10.10"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.9.0"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.10.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.1"
+  test:
+    dependency: "direct dev"
+    description:
+      name: test
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.17.12"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.3"
+  test_core:
+    dependency: transitive
+    description:
+      name: test_core
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.2"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.3.1"
+  vm_service:
+    dependency: transitive
+    description:
+      name: vm_service
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "7.5.0"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.1"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.0"
+  webkit_inspection_protocol:
+    dependency: transitive
+    description:
+      name: webkit_inspection_protocol
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.0"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.1"
+sdks:
+  dart: ">=2.16.0 <3.0.0"
diff --git a/mobile/openapi/pubspec.yaml b/mobile/openapi/pubspec.yaml
new file mode 100644
index 0000000000..00043ba660
--- /dev/null
+++ b/mobile/openapi/pubspec.yaml
@@ -0,0 +1,16 @@
+#
+# AUTO-GENERATED FILE, DO NOT MODIFY!
+#
+
+name: 'openapi'
+version: '1.0.0'
+description: 'OpenAPI API client'
+homepage: 'homepage'
+environment:
+  sdk: '>=2.12.0 <3.0.0'
+dependencies:
+  http: '>=0.13.0 <0.14.0'
+  intl: '^0.17.0'
+  meta: '^1.1.8'
+dev_dependencies:
+  test: '>=1.16.0 <1.18.0'
diff --git a/mobile/openapi/test/add_assets_dto_test.dart b/mobile/openapi/test/add_assets_dto_test.dart
new file mode 100644
index 0000000000..ec660c4000
--- /dev/null
+++ b/mobile/openapi/test/add_assets_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AddAssetsDto
+void main() {
+  // final instance = AddAssetsDto();
+
+  group('test AddAssetsDto', () {
+    // List<String> assetIds (default value: const [])
+    test('to test the property `assetIds`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/add_users_dto_test.dart b/mobile/openapi/test/add_users_dto_test.dart
new file mode 100644
index 0000000000..3dadfd8b4d
--- /dev/null
+++ b/mobile/openapi/test/add_users_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AddUsersDto
+void main() {
+  // final instance = AddUsersDto();
+
+  group('test AddUsersDto', () {
+    // List<String> sharedUserIds (default value: const [])
+    test('to test the property `sharedUserIds`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/admin_signup_response_dto_test.dart b/mobile/openapi/test/admin_signup_response_dto_test.dart
new file mode 100644
index 0000000000..d71763a327
--- /dev/null
+++ b/mobile/openapi/test/admin_signup_response_dto_test.dart
@@ -0,0 +1,47 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AdminSignupResponseDto
+void main() {
+  // final instance = AdminSignupResponseDto();
+
+  group('test AdminSignupResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String email
+    test('to test the property `email`', () async {
+      // TODO
+    });
+
+    // String firstName
+    test('to test the property `firstName`', () async {
+      // TODO
+    });
+
+    // String lastName
+    test('to test the property `lastName`', () async {
+      // TODO
+    });
+
+    // String createdAt
+    test('to test the property `createdAt`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/album_api_test.dart b/mobile/openapi/test/album_api_test.dart
new file mode 100644
index 0000000000..f13e7fa762
--- /dev/null
+++ b/mobile/openapi/test/album_api_test.dart
@@ -0,0 +1,66 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+
+/// tests for AlbumApi
+void main() {
+  // final instance = AlbumApi();
+
+  group('tests for AlbumApi', () {
+    //Future<AlbumResponseDto> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto) async
+    test('test addAssetsToAlbum', () async {
+      // TODO
+    });
+
+    //Future<AlbumResponseDto> addUsersToAlbum(String albumId, AddUsersDto addUsersDto) async
+    test('test addUsersToAlbum', () async {
+      // TODO
+    });
+
+    //Future<AlbumResponseDto> createAlbum(CreateAlbumDto createAlbumDto) async
+    test('test createAlbum', () async {
+      // TODO
+    });
+
+    //Future deleteAlbum(String albumId) async
+    test('test deleteAlbum', () async {
+      // TODO
+    });
+
+    //Future<AlbumResponseDto> getAlbumInfo(String albumId) async
+    test('test getAlbumInfo', () async {
+      // TODO
+    });
+
+    //Future<List<AlbumResponseDto>> getAllAlbums({ bool shared }) async
+    test('test getAllAlbums', () async {
+      // TODO
+    });
+
+    //Future removeAssetFromAlbum(String albumId, RemoveAssetsDto removeAssetsDto) async
+    test('test removeAssetFromAlbum', () async {
+      // TODO
+    });
+
+    //Future removeUserFromAlbum(String albumId, String userId) async
+    test('test removeUserFromAlbum', () async {
+      // TODO
+    });
+
+    //Future<AlbumResponseDto> updateAlbumInfo(String albumId, UpdateAlbumDto updateAlbumDto) async
+    test('test updateAlbumInfo', () async {
+      // TODO
+    });
+
+  });
+}
diff --git a/mobile/openapi/test/album_response_dto_test.dart b/mobile/openapi/test/album_response_dto_test.dart
new file mode 100644
index 0000000000..d1ad631e9a
--- /dev/null
+++ b/mobile/openapi/test/album_response_dto_test.dart
@@ -0,0 +1,62 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AlbumResponseDto
+void main() {
+  // final instance = AlbumResponseDto();
+
+  group('test AlbumResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String ownerId
+    test('to test the property `ownerId`', () async {
+      // TODO
+    });
+
+    // String albumName
+    test('to test the property `albumName`', () async {
+      // TODO
+    });
+
+    // String createdAt
+    test('to test the property `createdAt`', () async {
+      // TODO
+    });
+
+    // String albumThumbnailAssetId
+    test('to test the property `albumThumbnailAssetId`', () async {
+      // TODO
+    });
+
+    // bool shared
+    test('to test the property `shared`', () async {
+      // TODO
+    });
+
+    // List<UserResponseDto> sharedUsers (default value: const [])
+    test('to test the property `sharedUsers`', () async {
+      // TODO
+    });
+
+    // List<AssetResponseDto> assets (default value: const [])
+    test('to test the property `assets`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart
new file mode 100644
index 0000000000..b855f19ea8
--- /dev/null
+++ b/mobile/openapi/test/asset_api_test.dart
@@ -0,0 +1,102 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+
+/// tests for AssetApi
+void main() {
+  // final instance = AssetApi();
+
+  group('tests for AssetApi', () {
+    // 
+    //
+    // Check duplicated asset before uploading - for Web upload used
+    //
+    //Future<CheckDuplicateAssetResponseDto> checkDuplicateAsset(CheckDuplicateAssetDto checkDuplicateAssetDto) async
+    test('test checkDuplicateAsset', () async {
+      // TODO
+    });
+
+    //Future deleteAsset(DeleteAssetDto deleteAssetDto) async
+    test('test deleteAsset', () async {
+      // TODO
+    });
+
+    //Future<Object> downloadFile(String aid, String did, { bool isThumb, bool isWeb }) async
+    test('test downloadFile', () async {
+      // TODO
+    });
+
+    // 
+    //
+    // Get all AssetEntity belong to the user
+    //
+    //Future<List<AssetResponseDto>> getAllAssets() async
+    test('test getAllAssets', () async {
+      // TODO
+    });
+
+    // 
+    //
+    // Get a single asset's information
+    //
+    //Future<AssetResponseDto> getAssetById(String assetId) async
+    test('test getAssetById', () async {
+      // TODO
+    });
+
+    //Future<List<Object>> getAssetSearchTerms() async
+    test('test getAssetSearchTerms', () async {
+      // TODO
+    });
+
+    //Future<Object> getAssetThumbnail(String assetId) async
+    test('test getAssetThumbnail', () async {
+      // TODO
+    });
+
+    //Future<List<CuratedLocationsResponseDto>> getCuratedLocations() async
+    test('test getCuratedLocations', () async {
+      // TODO
+    });
+
+    //Future<List<CuratedObjectsResponseDto>> getCuratedObjects() async
+    test('test getCuratedObjects', () async {
+      // TODO
+    });
+
+    // 
+    //
+    // Get all asset of a device that are in the database, ID only.
+    //
+    //Future<List<String>> getUserAssetsByDeviceId(String deviceId) async
+    test('test getUserAssetsByDeviceId', () async {
+      // TODO
+    });
+
+    //Future<List<AssetResponseDto>> searchAsset(SearchAssetDto searchAssetDto) async
+    test('test searchAsset', () async {
+      // TODO
+    });
+
+    //Future<Object> serveFile(String aid, String did, { bool isThumb, bool isWeb }) async
+    test('test serveFile', () async {
+      // TODO
+    });
+
+    //Future<AssetFileUploadResponseDto> uploadFile(MultipartFile assetData) async
+    test('test uploadFile', () async {
+      // TODO
+    });
+
+  });
+}
diff --git a/mobile/openapi/test/asset_file_upload_response_dto_test.dart b/mobile/openapi/test/asset_file_upload_response_dto_test.dart
new file mode 100644
index 0000000000..c53ca650ca
--- /dev/null
+++ b/mobile/openapi/test/asset_file_upload_response_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AssetFileUploadResponseDto
+void main() {
+  // final instance = AssetFileUploadResponseDto();
+
+  group('test AssetFileUploadResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/asset_response_dto_test.dart b/mobile/openapi/test/asset_response_dto_test.dart
new file mode 100644
index 0000000000..95e9527bda
--- /dev/null
+++ b/mobile/openapi/test/asset_response_dto_test.dart
@@ -0,0 +1,102 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AssetResponseDto
+void main() {
+  // final instance = AssetResponseDto();
+
+  group('test AssetResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String deviceAssetId
+    test('to test the property `deviceAssetId`', () async {
+      // TODO
+    });
+
+    // String ownerId
+    test('to test the property `ownerId`', () async {
+      // TODO
+    });
+
+    // String deviceId
+    test('to test the property `deviceId`', () async {
+      // TODO
+    });
+
+    // String type
+    test('to test the property `type`', () async {
+      // TODO
+    });
+
+    // String originalPath
+    test('to test the property `originalPath`', () async {
+      // TODO
+    });
+
+    // String resizePath
+    test('to test the property `resizePath`', () async {
+      // TODO
+    });
+
+    // String createdAt
+    test('to test the property `createdAt`', () async {
+      // TODO
+    });
+
+    // String modifiedAt
+    test('to test the property `modifiedAt`', () async {
+      // TODO
+    });
+
+    // bool isFavorite
+    test('to test the property `isFavorite`', () async {
+      // TODO
+    });
+
+    // String mimeType
+    test('to test the property `mimeType`', () async {
+      // TODO
+    });
+
+    // String duration
+    test('to test the property `duration`', () async {
+      // TODO
+    });
+
+    // String webpPath
+    test('to test the property `webpPath`', () async {
+      // TODO
+    });
+
+    // String encodedVideoPath
+    test('to test the property `encodedVideoPath`', () async {
+      // TODO
+    });
+
+    // ExifResponseDto exifInfo
+    test('to test the property `exifInfo`', () async {
+      // TODO
+    });
+
+    // SmartInfoResponseDto smartInfo
+    test('to test the property `smartInfo`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/asset_type_enum_test.dart b/mobile/openapi/test/asset_type_enum_test.dart
new file mode 100644
index 0000000000..a826ee679f
--- /dev/null
+++ b/mobile/openapi/test/asset_type_enum_test.dart
@@ -0,0 +1,21 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for AssetTypeEnum
+void main() {
+
+  group('test AssetTypeEnum', () {
+
+  });
+
+}
diff --git a/mobile/openapi/test/authentication_api_test.dart b/mobile/openapi/test/authentication_api_test.dart
new file mode 100644
index 0000000000..2ab9205f81
--- /dev/null
+++ b/mobile/openapi/test/authentication_api_test.dart
@@ -0,0 +1,36 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+
+/// tests for AuthenticationApi
+void main() {
+  // final instance = AuthenticationApi();
+
+  group('tests for AuthenticationApi', () {
+    //Future<AdminSignupResponseDto> adminSignUp(SignUpDto signUpDto) async
+    test('test adminSignUp', () async {
+      // TODO
+    });
+
+    //Future<LoginResponseDto> login(LoginCredentialDto loginCredentialDto) async
+    test('test login', () async {
+      // TODO
+    });
+
+    //Future<Object> validateAccessToken() async
+    test('test validateAccessToken', () async {
+      // TODO
+    });
+
+  });
+}
diff --git a/mobile/openapi/test/check_duplicate_asset_dto_test.dart b/mobile/openapi/test/check_duplicate_asset_dto_test.dart
new file mode 100644
index 0000000000..2e8572da45
--- /dev/null
+++ b/mobile/openapi/test/check_duplicate_asset_dto_test.dart
@@ -0,0 +1,32 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CheckDuplicateAssetDto
+void main() {
+  // final instance = CheckDuplicateAssetDto();
+
+  group('test CheckDuplicateAssetDto', () {
+    // String deviceAssetId
+    test('to test the property `deviceAssetId`', () async {
+      // TODO
+    });
+
+    // String deviceId
+    test('to test the property `deviceId`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/check_duplicate_asset_response_dto_test.dart b/mobile/openapi/test/check_duplicate_asset_response_dto_test.dart
new file mode 100644
index 0000000000..fbe09b7761
--- /dev/null
+++ b/mobile/openapi/test/check_duplicate_asset_response_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CheckDuplicateAssetResponseDto
+void main() {
+  // final instance = CheckDuplicateAssetResponseDto();
+
+  group('test CheckDuplicateAssetResponseDto', () {
+    // bool isExist
+    test('to test the property `isExist`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/create_album_dto_test.dart b/mobile/openapi/test/create_album_dto_test.dart
new file mode 100644
index 0000000000..48263d393a
--- /dev/null
+++ b/mobile/openapi/test/create_album_dto_test.dart
@@ -0,0 +1,37 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CreateAlbumDto
+void main() {
+  // final instance = CreateAlbumDto();
+
+  group('test CreateAlbumDto', () {
+    // String albumName
+    test('to test the property `albumName`', () async {
+      // TODO
+    });
+
+    // List<String> sharedWithUserIds (default value: const [])
+    test('to test the property `sharedWithUserIds`', () async {
+      // TODO
+    });
+
+    // List<String> assetIds (default value: const [])
+    test('to test the property `assetIds`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/create_device_info_dto_test.dart b/mobile/openapi/test/create_device_info_dto_test.dart
new file mode 100644
index 0000000000..fe250e7e7f
--- /dev/null
+++ b/mobile/openapi/test/create_device_info_dto_test.dart
@@ -0,0 +1,37 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CreateDeviceInfoDto
+void main() {
+  // final instance = CreateDeviceInfoDto();
+
+  group('test CreateDeviceInfoDto', () {
+    // String deviceId
+    test('to test the property `deviceId`', () async {
+      // TODO
+    });
+
+    // String deviceType
+    test('to test the property `deviceType`', () async {
+      // TODO
+    });
+
+    // bool isAutoBackup
+    test('to test the property `isAutoBackup`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/create_profile_image_response_dto_test.dart b/mobile/openapi/test/create_profile_image_response_dto_test.dart
new file mode 100644
index 0000000000..83fee5b295
--- /dev/null
+++ b/mobile/openapi/test/create_profile_image_response_dto_test.dart
@@ -0,0 +1,32 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CreateProfileImageResponseDto
+void main() {
+  // final instance = CreateProfileImageResponseDto();
+
+  group('test CreateProfileImageResponseDto', () {
+    // String userId
+    test('to test the property `userId`', () async {
+      // TODO
+    });
+
+    // String profileImagePath
+    test('to test the property `profileImagePath`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/create_user_dto_test.dart b/mobile/openapi/test/create_user_dto_test.dart
new file mode 100644
index 0000000000..8e2e7d445a
--- /dev/null
+++ b/mobile/openapi/test/create_user_dto_test.dart
@@ -0,0 +1,42 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CreateUserDto
+void main() {
+  // final instance = CreateUserDto();
+
+  group('test CreateUserDto', () {
+    // String email
+    test('to test the property `email`', () async {
+      // TODO
+    });
+
+    // String password
+    test('to test the property `password`', () async {
+      // TODO
+    });
+
+    // String firstName
+    test('to test the property `firstName`', () async {
+      // TODO
+    });
+
+    // String lastName
+    test('to test the property `lastName`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/curated_locations_response_dto_test.dart b/mobile/openapi/test/curated_locations_response_dto_test.dart
new file mode 100644
index 0000000000..da4cccf3da
--- /dev/null
+++ b/mobile/openapi/test/curated_locations_response_dto_test.dart
@@ -0,0 +1,47 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CuratedLocationsResponseDto
+void main() {
+  // final instance = CuratedLocationsResponseDto();
+
+  group('test CuratedLocationsResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String city
+    test('to test the property `city`', () async {
+      // TODO
+    });
+
+    // String resizePath
+    test('to test the property `resizePath`', () async {
+      // TODO
+    });
+
+    // String deviceAssetId
+    test('to test the property `deviceAssetId`', () async {
+      // TODO
+    });
+
+    // String deviceId
+    test('to test the property `deviceId`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/curated_objects_response_dto_test.dart b/mobile/openapi/test/curated_objects_response_dto_test.dart
new file mode 100644
index 0000000000..8ef640bd92
--- /dev/null
+++ b/mobile/openapi/test/curated_objects_response_dto_test.dart
@@ -0,0 +1,47 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for CuratedObjectsResponseDto
+void main() {
+  // final instance = CuratedObjectsResponseDto();
+
+  group('test CuratedObjectsResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String object
+    test('to test the property `object`', () async {
+      // TODO
+    });
+
+    // String resizePath
+    test('to test the property `resizePath`', () async {
+      // TODO
+    });
+
+    // String deviceAssetId
+    test('to test the property `deviceAssetId`', () async {
+      // TODO
+    });
+
+    // String deviceId
+    test('to test the property `deviceId`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/delete_asset_dto_test.dart b/mobile/openapi/test/delete_asset_dto_test.dart
new file mode 100644
index 0000000000..4e04b65e9b
--- /dev/null
+++ b/mobile/openapi/test/delete_asset_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for DeleteAssetDto
+void main() {
+  // final instance = DeleteAssetDto();
+
+  group('test DeleteAssetDto', () {
+    // List<String> ids (default value: const [])
+    test('to test the property `ids`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/delete_asset_response_dto_test.dart b/mobile/openapi/test/delete_asset_response_dto_test.dart
new file mode 100644
index 0000000000..de09a5b31a
--- /dev/null
+++ b/mobile/openapi/test/delete_asset_response_dto_test.dart
@@ -0,0 +1,32 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for DeleteAssetResponseDto
+void main() {
+  // final instance = DeleteAssetResponseDto();
+
+  group('test DeleteAssetResponseDto', () {
+    // DeleteAssetStatus status
+    test('to test the property `status`', () async {
+      // TODO
+    });
+
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/delete_asset_status_test.dart b/mobile/openapi/test/delete_asset_status_test.dart
new file mode 100644
index 0000000000..0dfc82ba61
--- /dev/null
+++ b/mobile/openapi/test/delete_asset_status_test.dart
@@ -0,0 +1,21 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for DeleteAssetStatus
+void main() {
+
+  group('test DeleteAssetStatus', () {
+
+  });
+
+}
diff --git a/mobile/openapi/test/device_info_api_test.dart b/mobile/openapi/test/device_info_api_test.dart
new file mode 100644
index 0000000000..ce297de5da
--- /dev/null
+++ b/mobile/openapi/test/device_info_api_test.dart
@@ -0,0 +1,31 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+
+/// tests for DeviceInfoApi
+void main() {
+  // final instance = DeviceInfoApi();
+
+  group('tests for DeviceInfoApi', () {
+    //Future<DeviceInfoResponseDto> createDeviceInfo(CreateDeviceInfoDto createDeviceInfoDto) async
+    test('test createDeviceInfo', () async {
+      // TODO
+    });
+
+    //Future<DeviceInfoResponseDto> updateDeviceInfo(Object body) async
+    test('test updateDeviceInfo', () async {
+      // TODO
+    });
+
+  });
+}
diff --git a/mobile/openapi/test/device_info_response_dto_test.dart b/mobile/openapi/test/device_info_response_dto_test.dart
new file mode 100644
index 0000000000..748f95d09d
--- /dev/null
+++ b/mobile/openapi/test/device_info_response_dto_test.dart
@@ -0,0 +1,57 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for DeviceInfoResponseDto
+void main() {
+  // final instance = DeviceInfoResponseDto();
+
+  group('test DeviceInfoResponseDto', () {
+    // num id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String userId
+    test('to test the property `userId`', () async {
+      // TODO
+    });
+
+    // String deviceId
+    test('to test the property `deviceId`', () async {
+      // TODO
+    });
+
+    // String deviceType
+    test('to test the property `deviceType`', () async {
+      // TODO
+    });
+
+    // String notificationToken
+    test('to test the property `notificationToken`', () async {
+      // TODO
+    });
+
+    // String createdAt
+    test('to test the property `createdAt`', () async {
+      // TODO
+    });
+
+    // bool isAutoBackup
+    test('to test the property `isAutoBackup`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/device_type_enum_test.dart b/mobile/openapi/test/device_type_enum_test.dart
new file mode 100644
index 0000000000..27b29a53e0
--- /dev/null
+++ b/mobile/openapi/test/device_type_enum_test.dart
@@ -0,0 +1,21 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for DeviceTypeEnum
+void main() {
+
+  group('test DeviceTypeEnum', () {
+
+  });
+
+}
diff --git a/mobile/openapi/test/exif_response_dto_test.dart b/mobile/openapi/test/exif_response_dto_test.dart
new file mode 100644
index 0000000000..bf5164773d
--- /dev/null
+++ b/mobile/openapi/test/exif_response_dto_test.dart
@@ -0,0 +1,122 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for ExifResponseDto
+void main() {
+  // final instance = ExifResponseDto();
+
+  group('test ExifResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String make
+    test('to test the property `make`', () async {
+      // TODO
+    });
+
+    // String model
+    test('to test the property `model`', () async {
+      // TODO
+    });
+
+    // String imageName
+    test('to test the property `imageName`', () async {
+      // TODO
+    });
+
+    // num exifImageWidth
+    test('to test the property `exifImageWidth`', () async {
+      // TODO
+    });
+
+    // num exifImageHeight
+    test('to test the property `exifImageHeight`', () async {
+      // TODO
+    });
+
+    // num fileSizeInByte
+    test('to test the property `fileSizeInByte`', () async {
+      // TODO
+    });
+
+    // String orientation
+    test('to test the property `orientation`', () async {
+      // TODO
+    });
+
+    // DateTime dateTimeOriginal
+    test('to test the property `dateTimeOriginal`', () async {
+      // TODO
+    });
+
+    // DateTime modifyDate
+    test('to test the property `modifyDate`', () async {
+      // TODO
+    });
+
+    // String lensModel
+    test('to test the property `lensModel`', () async {
+      // TODO
+    });
+
+    // num fNumber
+    test('to test the property `fNumber`', () async {
+      // TODO
+    });
+
+    // num focalLength
+    test('to test the property `focalLength`', () async {
+      // TODO
+    });
+
+    // num iso
+    test('to test the property `iso`', () async {
+      // TODO
+    });
+
+    // num exposureTime
+    test('to test the property `exposureTime`', () async {
+      // TODO
+    });
+
+    // num latitude
+    test('to test the property `latitude`', () async {
+      // TODO
+    });
+
+    // num longitude
+    test('to test the property `longitude`', () async {
+      // TODO
+    });
+
+    // String city
+    test('to test the property `city`', () async {
+      // TODO
+    });
+
+    // String state
+    test('to test the property `state`', () async {
+      // TODO
+    });
+
+    // String country
+    test('to test the property `country`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/login_credential_dto_test.dart b/mobile/openapi/test/login_credential_dto_test.dart
new file mode 100644
index 0000000000..9995af7304
--- /dev/null
+++ b/mobile/openapi/test/login_credential_dto_test.dart
@@ -0,0 +1,32 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for LoginCredentialDto
+void main() {
+  // final instance = LoginCredentialDto();
+
+  group('test LoginCredentialDto', () {
+    // String email
+    test('to test the property `email`', () async {
+      // TODO
+    });
+
+    // String password
+    test('to test the property `password`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/login_response_dto_test.dart b/mobile/openapi/test/login_response_dto_test.dart
new file mode 100644
index 0000000000..f707799798
--- /dev/null
+++ b/mobile/openapi/test/login_response_dto_test.dart
@@ -0,0 +1,62 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for LoginResponseDto
+void main() {
+  // final instance = LoginResponseDto();
+
+  group('test LoginResponseDto', () {
+    // String accessToken
+    test('to test the property `accessToken`', () async {
+      // TODO
+    });
+
+    // String userId
+    test('to test the property `userId`', () async {
+      // TODO
+    });
+
+    // String userEmail
+    test('to test the property `userEmail`', () async {
+      // TODO
+    });
+
+    // String firstName
+    test('to test the property `firstName`', () async {
+      // TODO
+    });
+
+    // String lastName
+    test('to test the property `lastName`', () async {
+      // TODO
+    });
+
+    // String profileImagePath
+    test('to test the property `profileImagePath`', () async {
+      // TODO
+    });
+
+    // bool isAdmin
+    test('to test the property `isAdmin`', () async {
+      // TODO
+    });
+
+    // bool shouldChangePassword
+    test('to test the property `shouldChangePassword`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/remove_assets_dto_test.dart b/mobile/openapi/test/remove_assets_dto_test.dart
new file mode 100644
index 0000000000..4e5b48bcbc
--- /dev/null
+++ b/mobile/openapi/test/remove_assets_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for RemoveAssetsDto
+void main() {
+  // final instance = RemoveAssetsDto();
+
+  group('test RemoveAssetsDto', () {
+    // List<String> assetIds (default value: const [])
+    test('to test the property `assetIds`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/search_asset_dto_test.dart b/mobile/openapi/test/search_asset_dto_test.dart
new file mode 100644
index 0000000000..60021265cd
--- /dev/null
+++ b/mobile/openapi/test/search_asset_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for SearchAssetDto
+void main() {
+  // final instance = SearchAssetDto();
+
+  group('test SearchAssetDto', () {
+    // String searchTerm
+    test('to test the property `searchTerm`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/server_info_api_test.dart b/mobile/openapi/test/server_info_api_test.dart
new file mode 100644
index 0000000000..540f1280f0
--- /dev/null
+++ b/mobile/openapi/test/server_info_api_test.dart
@@ -0,0 +1,36 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+
+/// tests for ServerInfoApi
+void main() {
+  // final instance = ServerInfoApi();
+
+  group('tests for ServerInfoApi', () {
+    //Future<ServerInfoResponseDto> getServerInfo() async
+    test('test getServerInfo', () async {
+      // TODO
+    });
+
+    //Future<ServerVersionReponseDto> getServerVersion() async
+    test('test getServerVersion', () async {
+      // TODO
+    });
+
+    //Future<ServerPingResponse> pingServer() async
+    test('test pingServer', () async {
+      // TODO
+    });
+
+  });
+}
diff --git a/mobile/openapi/test/server_info_response_dto_test.dart b/mobile/openapi/test/server_info_response_dto_test.dart
new file mode 100644
index 0000000000..4deaa09a9e
--- /dev/null
+++ b/mobile/openapi/test/server_info_response_dto_test.dart
@@ -0,0 +1,57 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for ServerInfoResponseDto
+void main() {
+  // final instance = ServerInfoResponseDto();
+
+  group('test ServerInfoResponseDto', () {
+    // String diskSize
+    test('to test the property `diskSize`', () async {
+      // TODO
+    });
+
+    // String diskUse
+    test('to test the property `diskUse`', () async {
+      // TODO
+    });
+
+    // String diskAvailable
+    test('to test the property `diskAvailable`', () async {
+      // TODO
+    });
+
+    // num diskSizeRaw
+    test('to test the property `diskSizeRaw`', () async {
+      // TODO
+    });
+
+    // num diskUseRaw
+    test('to test the property `diskUseRaw`', () async {
+      // TODO
+    });
+
+    // num diskAvailableRaw
+    test('to test the property `diskAvailableRaw`', () async {
+      // TODO
+    });
+
+    // num diskUsagePercentage
+    test('to test the property `diskUsagePercentage`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/server_ping_response_test.dart b/mobile/openapi/test/server_ping_response_test.dart
new file mode 100644
index 0000000000..cb72680176
--- /dev/null
+++ b/mobile/openapi/test/server_ping_response_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for ServerPingResponse
+void main() {
+  // final instance = ServerPingResponse();
+
+  group('test ServerPingResponse', () {
+    // String res
+    test('to test the property `res`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/server_version_reponse_dto_test.dart b/mobile/openapi/test/server_version_reponse_dto_test.dart
new file mode 100644
index 0000000000..2c71afa994
--- /dev/null
+++ b/mobile/openapi/test/server_version_reponse_dto_test.dart
@@ -0,0 +1,42 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for ServerVersionReponseDto
+void main() {
+  // final instance = ServerVersionReponseDto();
+
+  group('test ServerVersionReponseDto', () {
+    // num major
+    test('to test the property `major`', () async {
+      // TODO
+    });
+
+    // num minor
+    test('to test the property `minor`', () async {
+      // TODO
+    });
+
+    // num patch_
+    test('to test the property `patch_`', () async {
+      // TODO
+    });
+
+    // num build
+    test('to test the property `build`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/sign_up_dto_test.dart b/mobile/openapi/test/sign_up_dto_test.dart
new file mode 100644
index 0000000000..4c4d376b82
--- /dev/null
+++ b/mobile/openapi/test/sign_up_dto_test.dart
@@ -0,0 +1,42 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for SignUpDto
+void main() {
+  // final instance = SignUpDto();
+
+  group('test SignUpDto', () {
+    // String email
+    test('to test the property `email`', () async {
+      // TODO
+    });
+
+    // String password
+    test('to test the property `password`', () async {
+      // TODO
+    });
+
+    // String firstName
+    test('to test the property `firstName`', () async {
+      // TODO
+    });
+
+    // String lastName
+    test('to test the property `lastName`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/smart_info_response_dto_test.dart b/mobile/openapi/test/smart_info_response_dto_test.dart
new file mode 100644
index 0000000000..a5e901b7b3
--- /dev/null
+++ b/mobile/openapi/test/smart_info_response_dto_test.dart
@@ -0,0 +1,37 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for SmartInfoResponseDto
+void main() {
+  // final instance = SmartInfoResponseDto();
+
+  group('test SmartInfoResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // List<String> tags (default value: const [])
+    test('to test the property `tags`', () async {
+      // TODO
+    });
+
+    // List<String> objects (default value: const [])
+    test('to test the property `objects`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/update_album_dto_test.dart b/mobile/openapi/test/update_album_dto_test.dart
new file mode 100644
index 0000000000..a03c126564
--- /dev/null
+++ b/mobile/openapi/test/update_album_dto_test.dart
@@ -0,0 +1,32 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for UpdateAlbumDto
+void main() {
+  // final instance = UpdateAlbumDto();
+
+  group('test UpdateAlbumDto', () {
+    // String albumName
+    test('to test the property `albumName`', () async {
+      // TODO
+    });
+
+    // String ownerId
+    test('to test the property `ownerId`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/update_device_info_dto_test.dart b/mobile/openapi/test/update_device_info_dto_test.dart
new file mode 100644
index 0000000000..32bb953512
--- /dev/null
+++ b/mobile/openapi/test/update_device_info_dto_test.dart
@@ -0,0 +1,37 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for UpdateDeviceInfoDto
+void main() {
+  // final instance = UpdateDeviceInfoDto();
+
+  group('test UpdateDeviceInfoDto', () {
+    // String deviceId
+    test('to test the property `deviceId`', () async {
+      // TODO
+    });
+
+    // String deviceType
+    test('to test the property `deviceType`', () async {
+      // TODO
+    });
+
+    // bool isAutoBackup
+    test('to test the property `isAutoBackup`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/update_user_dto_test.dart b/mobile/openapi/test/update_user_dto_test.dart
new file mode 100644
index 0000000000..3010100d8e
--- /dev/null
+++ b/mobile/openapi/test/update_user_dto_test.dart
@@ -0,0 +1,57 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for UpdateUserDto
+void main() {
+  // final instance = UpdateUserDto();
+
+  group('test UpdateUserDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String password
+    test('to test the property `password`', () async {
+      // TODO
+    });
+
+    // String firstName
+    test('to test the property `firstName`', () async {
+      // TODO
+    });
+
+    // String lastName
+    test('to test the property `lastName`', () async {
+      // TODO
+    });
+
+    // bool isAdmin
+    test('to test the property `isAdmin`', () async {
+      // TODO
+    });
+
+    // bool shouldChangePassword
+    test('to test the property `shouldChangePassword`', () async {
+      // TODO
+    });
+
+    // String profileImagePath
+    test('to test the property `profileImagePath`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/user_api_test.dart b/mobile/openapi/test/user_api_test.dart
new file mode 100644
index 0000000000..0d93508eeb
--- /dev/null
+++ b/mobile/openapi/test/user_api_test.dart
@@ -0,0 +1,56 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+
+/// tests for UserApi
+void main() {
+  // final instance = UserApi();
+
+  group('tests for UserApi', () {
+    //Future<CreateProfileImageResponseDto> createProfileImage(MultipartFile file) async
+    test('test createProfileImage', () async {
+      // TODO
+    });
+
+    //Future<UserResponseDto> createUser(CreateUserDto createUserDto) async
+    test('test createUser', () async {
+      // TODO
+    });
+
+    //Future<List<UserResponseDto>> getAllUsers(bool isAll) async
+    test('test getAllUsers', () async {
+      // TODO
+    });
+
+    //Future<UserResponseDto> getMyUserInfo() async
+    test('test getMyUserInfo', () async {
+      // TODO
+    });
+
+    //Future<Object> getProfileImage(String userId) async
+    test('test getProfileImage', () async {
+      // TODO
+    });
+
+    //Future<UserCountResponseDto> getUserCount() async
+    test('test getUserCount', () async {
+      // TODO
+    });
+
+    //Future<UserResponseDto> updateUser(UpdateUserDto updateUserDto) async
+    test('test updateUser', () async {
+      // TODO
+    });
+
+  });
+}
diff --git a/mobile/openapi/test/user_count_response_dto_test.dart b/mobile/openapi/test/user_count_response_dto_test.dart
new file mode 100644
index 0000000000..1f99d9511e
--- /dev/null
+++ b/mobile/openapi/test/user_count_response_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for UserCountResponseDto
+void main() {
+  // final instance = UserCountResponseDto();
+
+  group('test UserCountResponseDto', () {
+    // num userCount
+    test('to test the property `userCount`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/user_response_dto_test.dart b/mobile/openapi/test/user_response_dto_test.dart
new file mode 100644
index 0000000000..861010fac6
--- /dev/null
+++ b/mobile/openapi/test/user_response_dto_test.dart
@@ -0,0 +1,62 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for UserResponseDto
+void main() {
+  // final instance = UserResponseDto();
+
+  group('test UserResponseDto', () {
+    // String id
+    test('to test the property `id`', () async {
+      // TODO
+    });
+
+    // String email
+    test('to test the property `email`', () async {
+      // TODO
+    });
+
+    // String firstName
+    test('to test the property `firstName`', () async {
+      // TODO
+    });
+
+    // String lastName
+    test('to test the property `lastName`', () async {
+      // TODO
+    });
+
+    // String createdAt
+    test('to test the property `createdAt`', () async {
+      // TODO
+    });
+
+    // String profileImagePath
+    test('to test the property `profileImagePath`', () async {
+      // TODO
+    });
+
+    // bool shouldChangePassword
+    test('to test the property `shouldChangePassword`', () async {
+      // TODO
+    });
+
+    // bool isAdmin
+    test('to test the property `isAdmin`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/openapi/test/validate_access_token_response_dto_test.dart b/mobile/openapi/test/validate_access_token_response_dto_test.dart
new file mode 100644
index 0000000000..fff824700c
--- /dev/null
+++ b/mobile/openapi/test/validate_access_token_response_dto_test.dart
@@ -0,0 +1,27 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.12
+
+// ignore_for_file: unused_element, unused_import
+// ignore_for_file: always_put_required_named_parameters_first
+// ignore_for_file: constant_identifier_names
+// ignore_for_file: lines_longer_than_80_chars
+
+import 'package:openapi/api.dart';
+import 'package:test/test.dart';
+
+// tests for ValidateAccessTokenResponseDto
+void main() {
+  // final instance = ValidateAccessTokenResponseDto();
+
+  group('test ValidateAccessTokenResponseDto', () {
+    // bool authStatus
+    test('to test the property `authStatus`', () async {
+      // TODO
+    });
+
+
+  });
+
+}
diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock
index 911cc10b19..ec0701fafe 100644
--- a/mobile/pubspec.lock
+++ b/mobile/pubspec.lock
@@ -637,6 +637,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.1"
+  openapi:
+    dependency: "direct main"
+    description:
+      path: openapi
+      relative: true
+    source: path
+    version: "1.0.0"
   package_config:
     dependency: transitive
     description:
diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml
index 6633f1146e..5e284e3e62 100644
--- a/mobile/pubspec.yaml
+++ b/mobile/pubspec.yaml
@@ -48,6 +48,9 @@ dependencies:
   collection: ^1.16.0
   http_parser: ^4.0.1
 
+  openapi:
+    path: openapi
+
 dev_dependencies:
   flutter_test:
     sdk: flutter
@@ -76,3 +79,7 @@ flutter_icons:
   image_path_ios: "assets/immich-logo-no-outline.png"
   android: true # can specify file name here e.g. "ic_launcher"
   ios: true # can specify file name here e.g. "My-Launcher-Icon
+
+analyzer:
+  exclude:
+    - openapi/**
diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts
index 45b5541496..2a7b30ab53 100644
--- a/server/apps/immich/src/api-v1/asset/asset.controller.ts
+++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts
@@ -19,11 +19,10 @@ import {
 } from '@nestjs/common';
 import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
 import { AssetService } from './asset.service';
-import { FileFieldsInterceptor, FileInterceptor } from '@nestjs/platform-express';
+import { FileInterceptor } from '@nestjs/platform-express';
 import { assetUploadOption } from '../../config/asset-upload.config';
 import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 import { ServeFileDto } from './dto/serve-file.dto';
-import { AssetEntity } from '@app/database/entities/asset.entity';
 import { Response as Res } from 'express';
 import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
 import { DeleteAssetDto } from './dto/delete-asset.dto';
@@ -43,6 +42,7 @@ import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-a
 import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
 import { CreateAssetDto } from './dto/create-asset.dto';
 import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
+import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
 
 @UseGuards(JwtAuthGuard)
 @ApiBearerAuth()
@@ -124,7 +124,7 @@ export class AssetController {
   }
 
   @Get('/searchTerm')
-  async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<String[]> {
+  async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<string[]> {
     return this.assetService.getAssetSearchTerm(authUser);
   }
 
@@ -164,7 +164,10 @@ export class AssetController {
   }
 
   @Delete('/')
-  async deleteAsset(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) assetIds: DeleteAssetDto) {
+  async deleteAsset(
+    @GetAuthUser() authUser: AuthUserDto,
+    @Body(ValidationPipe) assetIds: DeleteAssetDto,
+  ): Promise<DeleteAssetResponseDto[]> {
     const deleteAssetList: AssetResponseDto[] = [];
 
     for (const id of assetIds.ids) {
@@ -178,7 +181,7 @@ export class AssetController {
     const result = await this.assetService.deleteAssetById(authUser, assetIds);
 
     result.forEach((res) => {
-      deleteAssetList.filter((a) => a.id == res.id && res.status == 'success');
+      deleteAssetList.filter((a) => a.id == res.id && res.status == DeleteAssetStatusEnum.SUCCESS);
     });
 
     await this.backgroundTaskService.deleteFileOnDisk(deleteAssetList);
diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts
index 647b45a315..e92e3c855b 100644
--- a/server/apps/immich/src/api-v1/asset/asset.service.ts
+++ b/server/apps/immich/src/api-v1/asset/asset.service.ts
@@ -22,6 +22,7 @@ import { CuratedObjectsResponseDto } from './response-dto/curated-objects-respon
 import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
 import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
 import { CreateAssetDto } from './dto/create-asset.dto';
+import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
 
 const fileInfo = promisify(stat);
 
@@ -280,7 +281,7 @@ export class AssetService {
 
         return new StreamableFile(fileReadStream);
       } catch (e) {
-        Logger.error(`Cannot create read stream for asset ${asset.id}`, 'serveFile[IMAGE]');
+        Logger.error(`Cannot create read stream for asset ${asset.id} ${JSON.stringify(e)}`, 'serveFile[IMAGE]');
         throw new InternalServerErrorException(
           e,
           `Cannot read thumbnail file for asset ${asset.id} - contact your administrator`,
@@ -354,8 +355,8 @@ export class AssetService {
     }
   }
 
-  public async deleteAssetById(authUser: AuthUserDto, assetIds: DeleteAssetDto) {
-    const result = [];
+  public async deleteAssetById(authUser: AuthUserDto, assetIds: DeleteAssetDto): Promise<DeleteAssetResponseDto[]> {
+    const result: DeleteAssetResponseDto[] = [];
 
     const target = assetIds.ids;
     for (const assetId of target) {
@@ -367,12 +368,12 @@ export class AssetService {
       if (res.affected) {
         result.push({
           id: assetId,
-          status: 'success',
+          status: DeleteAssetStatusEnum.SUCCESS,
         });
       } else {
         result.push({
           id: assetId,
-          status: 'failed',
+          status: DeleteAssetStatusEnum.FAILED,
         });
       }
     }
diff --git a/server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts b/server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts
index ba57ee4df4..25ca5a835f 100644
--- a/server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts
+++ b/server/apps/immich/src/api-v1/asset/dto/asset-file-upload.dto.ts
@@ -1,7 +1,5 @@
-import { AssetType } from '@app/database/entities/asset.entity';
 import { ApiProperty } from '@nestjs/swagger';
-import { IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
-import { CreateAssetDto } from './create-asset.dto';
+import { IsNotEmpty } from 'class-validator';
 
 export class AssetFileUploadDto {
   @IsNotEmpty()
diff --git a/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts b/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts
index f1194ec4de..7a5bcd3c09 100644
--- a/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts
+++ b/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts
@@ -1,5 +1,6 @@
 import { IsNotEmpty, IsOptional } from 'class-validator';
 import { AssetType } from '@app/database/entities/asset.entity';
+import { ApiProperty } from '@nestjs/swagger';
 
 export class CreateAssetDto {
   @IsNotEmpty()
@@ -9,6 +10,7 @@ export class CreateAssetDto {
   deviceId!: string;
 
   @IsNotEmpty()
+  @ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
   assetType!: AssetType;
 
   @IsNotEmpty()
diff --git a/server/apps/immich/src/api-v1/asset/dto/get-all-asset-query.dto.ts b/server/apps/immich/src/api-v1/asset/dto/get-all-asset-query.dto.ts
deleted file mode 100644
index 67f5e319fa..0000000000
--- a/server/apps/immich/src/api-v1/asset/dto/get-all-asset-query.dto.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { IsOptional } from 'class-validator';
-
-export class GetAllAssetQueryDto {
-  @IsOptional()
-  nextPageKey?: string;
-}
diff --git a/server/apps/immich/src/api-v1/asset/dto/get-all-asset-response.dto.ts b/server/apps/immich/src/api-v1/asset/dto/get-all-asset-response.dto.ts
deleted file mode 100644
index 5af3e205aa..0000000000
--- a/server/apps/immich/src/api-v1/asset/dto/get-all-asset-response.dto.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { AssetEntity } from '@app/database/entities/asset.entity';
-
-// TODO: this doesn't seem to be used
-export class GetAllAssetReponseDto {
-  data!: Array<{ date: string; assets: Array<AssetEntity> }>;
-  count!: number;
-  nextPageKey!: string;
-}
diff --git a/server/apps/immich/src/api-v1/asset/dto/get-new-asset-query.dto.ts b/server/apps/immich/src/api-v1/asset/dto/get-new-asset-query.dto.ts
deleted file mode 100644
index 2d98f0fb59..0000000000
--- a/server/apps/immich/src/api-v1/asset/dto/get-new-asset-query.dto.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { IsNotEmpty } from 'class-validator';
-
-export class GetNewAssetQueryDto {
-  @IsNotEmpty()
-  latestDate!: string;
-}
diff --git a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts b/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts
deleted file mode 100644
index 891e14436b..0000000000
--- a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { PartialType } from '@nestjs/mapped-types';
-import { CreateAssetDto } from './create-asset.dto';
-
-export class UpdateAssetDto extends PartialType(CreateAssetDto) {}
diff --git a/server/apps/immich/src/api-v1/asset/dto/update-exif.dto.ts b/server/apps/immich/src/api-v1/asset/dto/update-exif.dto.ts
deleted file mode 100644
index 1bf5066649..0000000000
--- a/server/apps/immich/src/api-v1/asset/dto/update-exif.dto.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { PartialType } from '@nestjs/mapped-types';
-import { CreateExifDto } from './create-exif.dto';
-
-export class UpdateExifDto extends PartialType(CreateExifDto) {}
diff --git a/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts
index ef74145abf..5e4887fcd9 100644
--- a/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts
+++ b/server/apps/immich/src/api-v1/asset/response-dto/asset-response.dto.ts
@@ -1,4 +1,5 @@
 import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
+import { ApiProperty } from '@nestjs/swagger';
 import { ExifResponseDto, mapExif } from './exif-response.dto';
 import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto';
 
@@ -7,6 +8,8 @@ export class AssetResponseDto {
   deviceAssetId!: string;
   ownerId!: string;
   deviceId!: string;
+
+  @ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
   type!: AssetType;
   originalPath!: string;
   resizePath!: string | null;
diff --git a/server/apps/immich/src/api-v1/asset/response-dto/delete-asset-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/delete-asset-response.dto.ts
new file mode 100644
index 0000000000..86f6afdf18
--- /dev/null
+++ b/server/apps/immich/src/api-v1/asset/response-dto/delete-asset-response.dto.ts
@@ -0,0 +1,13 @@
+import { ApiProperty } from '@nestjs/swagger';
+
+export enum DeleteAssetStatusEnum {
+  SUCCESS = 'SUCCESS',
+  FAILED = 'FAILED',
+}
+
+export class DeleteAssetResponseDto {
+  id!: string;
+
+  @ApiProperty({ type: 'string', enum: DeleteAssetStatusEnum, enumName: 'DeleteAssetStatus' })
+  status!: DeleteAssetStatusEnum;
+}
diff --git a/server/apps/immich/src/api-v1/asset/response-dto/exif-response.dto.ts b/server/apps/immich/src/api-v1/asset/response-dto/exif-response.dto.ts
index 57b0c8e9c0..c43c55b4eb 100644
--- a/server/apps/immich/src/api-v1/asset/response-dto/exif-response.dto.ts
+++ b/server/apps/immich/src/api-v1/asset/response-dto/exif-response.dto.ts
@@ -1,26 +1,26 @@
 import { ExifEntity } from '@app/database/entities/exif.entity';
 
 export class ExifResponseDto {
-  id!: string;
-  make: string | null = null;
-  model: string | null = null;
-  imageName: string | null = null;
-  exifImageWidth: number | null = null;
-  exifImageHeight: number | null = null;
-  fileSizeInByte: number | null = null;
-  orientation: string | null = null;
-  dateTimeOriginal: Date | null = null;
-  modifyDate: Date | null = null;
-  lensModel: string | null = null;
-  fNumber: number | null = null;
-  focalLength: number | null = null;
-  iso: number | null = null;
-  exposureTime: number | null = null;
-  latitude: number | null = null;
-  longitude: number | null = null;
-  city: string | null = null;
-  state: string | null = null;
-  country: string | null = null;
+  id?: string | null = null;
+  make?: string | null = null;
+  model?: string | null = null;
+  imageName?: string | null = null;
+  exifImageWidth?: number | null = null;
+  exifImageHeight?: number | null = null;
+  fileSizeInByte?: number | null = null;
+  orientation?: string | null = null;
+  dateTimeOriginal?: Date | null = null;
+  modifyDate?: Date | null = null;
+  lensModel?: string | null = null;
+  fNumber?: number | null = null;
+  focalLength?: number | null = null;
+  iso?: number | null = null;
+  exposureTime?: number | null = null;
+  latitude?: number | null = null;
+  longitude?: number | null = null;
+  city?: string | null = null;
+  state?: string | null = null;
+  country?: string | null = null;
 }
 
 export function mapExif(entity: ExifEntity): ExifResponseDto {
diff --git a/server/apps/immich/src/api-v1/auth/response-dto/validate-asset-token-response.dto,.ts b/server/apps/immich/src/api-v1/auth/response-dto/validate-asset-token-response.dto,.ts
index 151d59e6f1..9d4d770de2 100644
--- a/server/apps/immich/src/api-v1/auth/response-dto/validate-asset-token-response.dto,.ts
+++ b/server/apps/immich/src/api-v1/auth/response-dto/validate-asset-token-response.dto,.ts
@@ -1,7 +1,10 @@
+import { ApiProperty } from '@nestjs/swagger';
+
 export class ValidateAccessTokenResponseDto {
   constructor(authStatus: boolean) {
     this.authStatus = authStatus;
   }
 
-  authStatus: boolean;
+  @ApiProperty({ type: 'boolean' })
+  authStatus!: boolean;
 }
diff --git a/server/apps/immich/src/api-v1/device-info/dto/create-device-info.dto.ts b/server/apps/immich/src/api-v1/device-info/dto/create-device-info.dto.ts
index a9db6ea435..29dddea818 100644
--- a/server/apps/immich/src/api-v1/device-info/dto/create-device-info.dto.ts
+++ b/server/apps/immich/src/api-v1/device-info/dto/create-device-info.dto.ts
@@ -1,11 +1,13 @@
 import { IsNotEmpty, IsOptional } from 'class-validator';
 import { DeviceType } from '@app/database/entities/device-info.entity';
+import { ApiProperty } from '@nestjs/swagger';
 
 export class CreateDeviceInfoDto {
   @IsNotEmpty()
   deviceId!: string;
 
   @IsNotEmpty()
+  @ApiProperty({ enumName: 'DeviceTypeEnum', enum: DeviceType })
   deviceType!: DeviceType;
 
   @IsOptional()
diff --git a/server/apps/immich/src/api-v1/device-info/dto/update-device-info.dto.ts b/server/apps/immich/src/api-v1/device-info/dto/update-device-info.dto.ts
index cd2be701a7..a047e852b5 100644
--- a/server/apps/immich/src/api-v1/device-info/dto/update-device-info.dto.ts
+++ b/server/apps/immich/src/api-v1/device-info/dto/update-device-info.dto.ts
@@ -1,4 +1,17 @@
+import { DeviceType } from '@app/database/entities/device-info.entity';
 import { PartialType } from '@nestjs/mapped-types';
+import { ApiProperty } from '@nestjs/swagger';
+import { IsNotEmpty, IsOptional } from 'class-validator';
 import { CreateDeviceInfoDto } from './create-device-info.dto';
 
-export class UpdateDeviceInfoDto extends PartialType(CreateDeviceInfoDto) {}
+export class UpdateDeviceInfoDto {
+  @IsNotEmpty()
+  deviceId!: string;
+
+  @IsNotEmpty()
+  @ApiProperty({ enumName: 'DeviceTypeEnum', enum: DeviceType })
+  deviceType!: DeviceType;
+
+  @IsOptional()
+  isAutoBackup?: boolean;
+}
diff --git a/server/apps/immich/src/api-v1/device-info/response-dto/create-device-info-response.dto.ts b/server/apps/immich/src/api-v1/device-info/response-dto/create-device-info-response.dto.ts
index e36389d332..5c1bcfadf9 100644
--- a/server/apps/immich/src/api-v1/device-info/response-dto/create-device-info-response.dto.ts
+++ b/server/apps/immich/src/api-v1/device-info/response-dto/create-device-info-response.dto.ts
@@ -1,11 +1,15 @@
 import { DeviceInfoEntity, DeviceType } from '@app/database/entities/device-info.entity';
+import { ApiProperty } from '@nestjs/swagger';
 
 export class DeviceInfoResponseDto {
+  @ApiProperty({ type: 'integer' })
   id!: number;
   userId!: string;
   deviceId!: string;
+
+  @ApiProperty({ enumName: 'DeviceTypeEnum', enum: DeviceType })
   deviceType!: DeviceType;
-  notificationToken!: string | null;
+
   createdAt!: string;
   isAutoBackup!: boolean;
 }
@@ -16,7 +20,6 @@ export function mapDeviceInfoResponse(entity: DeviceInfoEntity): DeviceInfoRespo
     userId: entity.userId,
     deviceId: entity.deviceId,
     deviceType: entity.deviceType,
-    notificationToken: entity.notificationToken,
     createdAt: entity.createdAt,
     isAutoBackup: entity.isAutoBackup,
   };
diff --git a/server/apps/immich/src/api-v1/server-info/response-dto/server-info-response.dto.ts b/server/apps/immich/src/api-v1/server-info/response-dto/server-info-response.dto.ts
index e0a5f5c2da..444292091b 100644
--- a/server/apps/immich/src/api-v1/server-info/response-dto/server-info-response.dto.ts
+++ b/server/apps/immich/src/api-v1/server-info/response-dto/server-info-response.dto.ts
@@ -1,9 +1,19 @@
+import { ApiProperty } from '@nestjs/swagger';
+
 export class ServerInfoResponseDto {
   diskSize!: string;
   diskUse!: string;
   diskAvailable!: string;
+
+  @ApiProperty({ type: 'integer' })
   diskSizeRaw!: number;
+
+  @ApiProperty({ type: 'integer' })
   diskUseRaw!: number;
+
+  @ApiProperty({ type: 'integer' })
   diskAvailableRaw!: number;
+
+  @ApiProperty({ type: 'number', format: 'float' })
   diskUsagePercentage!: number;
 }
diff --git a/server/apps/immich/src/api-v1/server-info/response-dto/server-version-response.dto.ts b/server/apps/immich/src/api-v1/server-info/response-dto/server-version-response.dto.ts
index d054d9b3ce..812f3b0714 100644
--- a/server/apps/immich/src/api-v1/server-info/response-dto/server-version-response.dto.ts
+++ b/server/apps/immich/src/api-v1/server-info/response-dto/server-version-response.dto.ts
@@ -1,8 +1,13 @@
+import { ApiProperty } from '@nestjs/swagger';
 import { IServerVersion } from 'apps/immich/src/constants/server_version.constant';
 
 export class ServerVersionReponseDto implements IServerVersion {
+  @ApiProperty({ type: 'integer' })
   major!: number;
+  @ApiProperty({ type: 'integer' })
   minor!: number;
+  @ApiProperty({ type: 'integer' })
   patch!: number;
+  @ApiProperty({ type: 'integer' })
   build!: number;
 }
diff --git a/server/apps/immich/src/api-v1/server-info/server-info.service.ts b/server/apps/immich/src/api-v1/server-info/server-info.service.ts
index 606161aa77..23de4123ea 100644
--- a/server/apps/immich/src/api-v1/server-info/server-info.service.ts
+++ b/server/apps/immich/src/api-v1/server-info/server-info.service.ts
@@ -5,7 +5,7 @@ import { APP_UPLOAD_LOCATION } from '../../constants/upload_location.constant';
 
 @Injectable()
 export class ServerInfoService {
-  async getServerInfo() {
+  async getServerInfo(): Promise<ServerInfoResponseDto> {
     const diskInfo = await diskusage.check(APP_UPLOAD_LOCATION);
 
     const usagePercentage = (((diskInfo.total - diskInfo.free) / diskInfo.total) * 100).toFixed(2);
diff --git a/server/apps/immich/src/api-v1/user/dto/create-profile-image.dto.ts b/server/apps/immich/src/api-v1/user/dto/create-profile-image.dto.ts
index a01f10f466..7b58ba5aa8 100644
--- a/server/apps/immich/src/api-v1/user/dto/create-profile-image.dto.ts
+++ b/server/apps/immich/src/api-v1/user/dto/create-profile-image.dto.ts
@@ -1,6 +1,7 @@
 import { ApiProperty } from '@nestjs/swagger';
+import { Express } from 'express';
 
 export class CreateProfileImageDto {
   @ApiProperty({ type: 'string', format: 'binary' })
-  file: any;
+  file!: Express.Multer.File;
 }
diff --git a/server/apps/immich/src/api-v1/user/response-dto/user-count-response.dto.ts b/server/apps/immich/src/api-v1/user/response-dto/user-count-response.dto.ts
index 63df372350..bcdc3a5b78 100644
--- a/server/apps/immich/src/api-v1/user/response-dto/user-count-response.dto.ts
+++ b/server/apps/immich/src/api-v1/user/response-dto/user-count-response.dto.ts
@@ -1,5 +1,7 @@
+import { ApiProperty } from '@nestjs/swagger';
 
 export class UserCountResponseDto {
+  @ApiProperty({ type: 'integer' })
   userCount!: number;
 }
 
@@ -7,4 +9,4 @@ export function mapUserCountResponse(count: number): UserCountResponseDto {
   return {
     userCount: count,
   };
-}
\ No newline at end of file
+}
diff --git a/server/apps/immich/src/api-v1/user/user.controller.ts b/server/apps/immich/src/api-v1/user/user.controller.ts
index 6ec543b1fa..3700050a42 100644
--- a/server/apps/immich/src/api-v1/user/user.controller.ts
+++ b/server/apps/immich/src/api-v1/user/user.controller.ts
@@ -11,6 +11,7 @@ import {
   UseInterceptors,
   UploadedFile,
   Response,
+  Request,
   StreamableFile,
   ParseBoolPipe,
 } from '@nestjs/common';
@@ -22,7 +23,7 @@ import { AdminRolesGuard } from '../../middlewares/admin-role-guard.middleware';
 import { UpdateUserDto } from './dto/update-user.dto';
 import { FileInterceptor } from '@nestjs/platform-express';
 import { profileImageUploadOption } from '../../config/profile-image-upload.config';
-import { Response as Res } from 'express';
+import { Response as Res, Request as Req } from 'express';
 import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
 import { UserResponseDto } from './response-dto/user-response.dto';
 import { UserCountResponseDto } from './response-dto/user-count-response.dto';
@@ -76,13 +77,16 @@ export class UserController {
   @ApiBearerAuth()
   @ApiConsumes('multipart/form-data')
   @ApiBody({
+    description: 'A new avatar for the user',
     type: CreateProfileImageDto,
   })
   @Post('/profile-image')
   async createProfileImage(
     @GetAuthUser() authUser: AuthUserDto,
     @UploadedFile() fileInfo: Express.Multer.File,
+    @Request() req: Req,
   ): Promise<CreateProfileImageResponseDto> {
+    console.log(req.body, req.file);
     return await this.userService.createProfileImage(authUser, fileInfo);
   }
 
diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json
index fadc2b713b..a70db8f2c1 100644
--- a/server/immich-openapi-specs.json
+++ b/server/immich-openapi-specs.json
@@ -1 +1 @@
-{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allObjects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allLocation":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/searchTerm":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"object"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":""}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"number"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"string"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}},"required":["id","make","model","imageName","exifImageWidth","exifImageHeight","fileSizeInByte","orientation","dateTimeOriginal","modifyDate","lensModel","fNumber","focalLength","iso","exposureTime","latitude","longitude","city","state","country"]},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"type":{"enum":["IMAGE","VIDEO","AUDIO","OTHER"],"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["id","deviceAssetId","ownerId","deviceId","type","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"}},"required":["isExist"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{}},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceId":{"type":"string"},"deviceType":{"type":"string","enum":["IOS","ANDROID","WEB"]},"isAutoBackup":{"type":"boolean"}},"required":["deviceId","deviceType"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"number"},"userId":{"type":"string"},"deviceId":{"type":"string"},"deviceType":{"enum":["IOS","ANDROID","WEB"],"type":"string"},"notificationToken":{"type":"string","nullable":true},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","userId","deviceId","deviceType","notificationToken","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{}},"ServerInfoResponseDto":{"type":"object","properties":{"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"},"diskSizeRaw":{"type":"number"},"diskUseRaw":{"type":"number"},"diskAvailableRaw":{"type":"number"},"diskUsagePercentage":{"type":"number"}},"required":["diskSize","diskUse","diskAvailable","diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"number"},"minor":{"type":"number"},"patch":{"type":"number"},"build":{"type":"number"}},"required":["major","minor","patch","build"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"ownerId":{"type":"string"}},"required":["albumName","ownerId"]}}}}
\ No newline at end of file
+{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allObjects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/allLocation":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/searchTerm":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"string","nullable":true,"default":null},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"fileSizeInByte":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"}},"required":["isExist"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer"},"diskUseRaw":{"type":"integer"},"diskAvailableRaw":{"type":"integer"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"ownerId":{"type":"string"}},"required":["albumName","ownerId"]}}}}
\ No newline at end of file
diff --git a/server/package.json b/server/package.json
index ab0910ac9c..7eccabdd05 100644
--- a/server/package.json
+++ b/server/package.json
@@ -23,7 +23,9 @@
     "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
     "test:e2e": "jest --config ./apps/immich/test/jest-e2e.json",
     "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
-    "api:generate-typescript": "rm -rf ../web/src/api/open-api && npx openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/api/open-api"
+    "api:typescript": "rm -rf ../web/src/api/open-api && npx openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ../web/src/api/open-api",
+    "api:dart": "npx openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi",
+    "api:generate": "npm run api:typescript && npm run api:dart"
   },
   "dependencies": {
     "@mapbox/mapbox-sdk": "^0.13.3",
diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts
index c94ef1437c..b61c24d1bc 100644
--- a/web/src/api/open-api/api.ts
+++ b/web/src/api/open-api/api.ts
@@ -158,6 +158,12 @@ export interface AssetFileUploadResponseDto {
  * @interface AssetResponseDto
  */
 export interface AssetResponseDto {
+    /**
+     * 
+     * @type {AssetTypeEnum}
+     * @memberof AssetResponseDto
+     */
+    'type': AssetTypeEnum;
     /**
      * 
      * @type {string}
@@ -182,12 +188,6 @@ export interface AssetResponseDto {
      * @memberof AssetResponseDto
      */
     'deviceId': string;
-    /**
-     * 
-     * @type {string}
-     * @memberof AssetResponseDto
-     */
-    'type': AssetResponseDtoTypeEnum;
     /**
      * 
      * @type {string}
@@ -255,15 +255,21 @@ export interface AssetResponseDto {
      */
     'smartInfo'?: SmartInfoResponseDto;
 }
+/**
+ * 
+ * @export
+ * @enum {string}
+ */
 
-export const AssetResponseDtoTypeEnum = {
+export const AssetTypeEnum = {
     Image: 'IMAGE',
     Video: 'VIDEO',
     Audio: 'AUDIO',
     Other: 'OTHER'
 } as const;
 
-export type AssetResponseDtoTypeEnum = typeof AssetResponseDtoTypeEnum[keyof typeof AssetResponseDtoTypeEnum];
+export type AssetTypeEnum = typeof AssetTypeEnum[keyof typeof AssetTypeEnum];
+
 
 /**
  * 
@@ -330,16 +336,16 @@ export interface CreateAlbumDto {
 export interface CreateDeviceInfoDto {
     /**
      * 
-     * @type {string}
+     * @type {DeviceTypeEnum}
      * @memberof CreateDeviceInfoDto
      */
-    'deviceId': string;
+    'deviceType': DeviceTypeEnum;
     /**
      * 
      * @type {string}
      * @memberof CreateDeviceInfoDto
      */
-    'deviceType': CreateDeviceInfoDtoDeviceTypeEnum;
+    'deviceId': string;
     /**
      * 
      * @type {boolean}
@@ -347,15 +353,6 @@ export interface CreateDeviceInfoDto {
      */
     'isAutoBackup'?: boolean;
 }
-
-export const CreateDeviceInfoDtoDeviceTypeEnum = {
-    Ios: 'IOS',
-    Android: 'ANDROID',
-    Web: 'WEB'
-} as const;
-
-export type CreateDeviceInfoDtoDeviceTypeEnum = typeof CreateDeviceInfoDtoDeviceTypeEnum[keyof typeof CreateDeviceInfoDtoDeviceTypeEnum];
-
 /**
  * 
  * @export
@@ -493,6 +490,39 @@ export interface DeleteAssetDto {
      */
     'ids': Array<string>;
 }
+/**
+ * 
+ * @export
+ * @interface DeleteAssetResponseDto
+ */
+export interface DeleteAssetResponseDto {
+    /**
+     * 
+     * @type {DeleteAssetStatus}
+     * @memberof DeleteAssetResponseDto
+     */
+    'status': DeleteAssetStatus;
+    /**
+     * 
+     * @type {string}
+     * @memberof DeleteAssetResponseDto
+     */
+    'id': string;
+}
+/**
+ * 
+ * @export
+ * @enum {string}
+ */
+
+export const DeleteAssetStatus = {
+    Success: 'SUCCESS',
+    Failed: 'FAILED'
+} as const;
+
+export type DeleteAssetStatus = typeof DeleteAssetStatus[keyof typeof DeleteAssetStatus];
+
+
 /**
  * 
  * @export
@@ -505,6 +535,12 @@ export interface DeviceInfoResponseDto {
      * @memberof DeviceInfoResponseDto
      */
     'id': number;
+    /**
+     * 
+     * @type {DeviceTypeEnum}
+     * @memberof DeviceInfoResponseDto
+     */
+    'deviceType': DeviceTypeEnum;
     /**
      * 
      * @type {string}
@@ -517,18 +553,6 @@ export interface DeviceInfoResponseDto {
      * @memberof DeviceInfoResponseDto
      */
     'deviceId': string;
-    /**
-     * 
-     * @type {string}
-     * @memberof DeviceInfoResponseDto
-     */
-    'deviceType': DeviceInfoResponseDtoDeviceTypeEnum;
-    /**
-     * 
-     * @type {string}
-     * @memberof DeviceInfoResponseDto
-     */
-    'notificationToken': string | null;
     /**
      * 
      * @type {string}
@@ -542,14 +566,20 @@ export interface DeviceInfoResponseDto {
      */
     'isAutoBackup': boolean;
 }
+/**
+ * 
+ * @export
+ * @enum {string}
+ */
 
-export const DeviceInfoResponseDtoDeviceTypeEnum = {
+export const DeviceTypeEnum = {
     Ios: 'IOS',
     Android: 'ANDROID',
     Web: 'WEB'
 } as const;
 
-export type DeviceInfoResponseDtoDeviceTypeEnum = typeof DeviceInfoResponseDtoDeviceTypeEnum[keyof typeof DeviceInfoResponseDtoDeviceTypeEnum];
+export type DeviceTypeEnum = typeof DeviceTypeEnum[keyof typeof DeviceTypeEnum];
+
 
 /**
  * 
@@ -562,121 +592,121 @@ export interface ExifResponseDto {
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'id': string;
+    'id'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'make': string | null;
+    'make'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'model': string | null;
+    'model'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'imageName': string | null;
+    'imageName'?: string | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'exifImageWidth': number | null;
+    'exifImageWidth'?: number | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'exifImageHeight': number | null;
+    'exifImageHeight'?: number | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'fileSizeInByte': number | null;
+    'fileSizeInByte'?: number | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'orientation': string | null;
+    'orientation'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'dateTimeOriginal': string | null;
+    'dateTimeOriginal'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'modifyDate': string | null;
+    'modifyDate'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'lensModel': string | null;
+    'lensModel'?: string | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'fNumber': number | null;
+    'fNumber'?: number | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'focalLength': number | null;
+    'focalLength'?: number | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'iso': number | null;
+    'iso'?: number | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'exposureTime': number | null;
+    'exposureTime'?: number | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'latitude': number | null;
+    'latitude'?: number | null;
     /**
      * 
      * @type {number}
      * @memberof ExifResponseDto
      */
-    'longitude': number | null;
+    'longitude'?: number | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'city': string | null;
+    'city'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'state': string | null;
+    'state'?: string | null;
     /**
      * 
      * @type {string}
      * @memberof ExifResponseDto
      */
-    'country': string | null;
+    'country'?: string | null;
 }
 /**
  * 
@@ -784,24 +814,6 @@ export interface SearchAssetDto {
  * @interface ServerInfoResponseDto
  */
 export interface ServerInfoResponseDto {
-    /**
-     * 
-     * @type {string}
-     * @memberof ServerInfoResponseDto
-     */
-    'diskSize': string;
-    /**
-     * 
-     * @type {string}
-     * @memberof ServerInfoResponseDto
-     */
-    'diskUse': string;
-    /**
-     * 
-     * @type {string}
-     * @memberof ServerInfoResponseDto
-     */
-    'diskAvailable': string;
     /**
      * 
      * @type {number}
@@ -826,6 +838,24 @@ export interface ServerInfoResponseDto {
      * @memberof ServerInfoResponseDto
      */
     'diskUsagePercentage': number;
+    /**
+     * 
+     * @type {string}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskSize': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskUse': string;
+    /**
+     * 
+     * @type {string}
+     * @memberof ServerInfoResponseDto
+     */
+    'diskAvailable': string;
 }
 /**
  * 
@@ -946,6 +976,31 @@ export interface UpdateAlbumDto {
      */
     'ownerId': string;
 }
+/**
+ * 
+ * @export
+ * @interface UpdateDeviceInfoDto
+ */
+export interface UpdateDeviceInfoDto {
+    /**
+     * 
+     * @type {DeviceTypeEnum}
+     * @memberof UpdateDeviceInfoDto
+     */
+    'deviceType': DeviceTypeEnum;
+    /**
+     * 
+     * @type {string}
+     * @memberof UpdateDeviceInfoDto
+     */
+    'deviceId': string;
+    /**
+     * 
+     * @type {boolean}
+     * @memberof UpdateDeviceInfoDto
+     */
+    'isAutoBackup'?: boolean;
+}
 /**
  * 
  * @export
@@ -1063,6 +1118,19 @@ export interface UserResponseDto {
      */
     'isAdmin': boolean;
 }
+/**
+ * 
+ * @export
+ * @interface ValidateAccessTokenResponseDto
+ */
+export interface ValidateAccessTokenResponseDto {
+    /**
+     * 
+     * @type {boolean}
+     * @memberof ValidateAccessTokenResponseDto
+     */
+    'authStatus': boolean;
+}
 
 /**
  * AlbumApi - axios parameter creator
@@ -2306,7 +2374,7 @@ export const AssetApiFp = function(configuration?: Configuration) {
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async deleteAsset(deleteAssetDto: DeleteAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+        async deleteAsset(deleteAssetDto: DeleteAssetDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<DeleteAssetResponseDto>>> {
             const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAsset(deleteAssetDto, options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
@@ -2349,7 +2417,7 @@ export const AssetApiFp = function(configuration?: Configuration) {
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async getAssetSearchTerms(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<object>>> {
+        async getAssetSearchTerms(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {
             const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetSearchTerms(options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
@@ -2451,7 +2519,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        deleteAsset(deleteAssetDto: DeleteAssetDto, options?: any): AxiosPromise<void> {
+        deleteAsset(deleteAssetDto: DeleteAssetDto, options?: any): AxiosPromise<Array<DeleteAssetResponseDto>> {
             return localVarFp.deleteAsset(deleteAssetDto, options).then((request) => request(axios, basePath));
         },
         /**
@@ -2490,7 +2558,7 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        getAssetSearchTerms(options?: any): AxiosPromise<Array<object>> {
+        getAssetSearchTerms(options?: any): AxiosPromise<Array<string>> {
             return localVarFp.getAssetSearchTerms(options).then((request) => request(axios, basePath));
         },
         /**
@@ -2863,7 +2931,7 @@ export const AuthenticationApiFp = function(configuration?: Configuration) {
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async validateAccessToken(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
+        async validateAccessToken(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ValidateAccessTokenResponseDto>> {
             const localVarAxiosArgs = await localVarAxiosParamCreator.validateAccessToken(options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
@@ -2900,7 +2968,7 @@ export const AuthenticationApiFactory = function (configuration?: Configuration,
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        validateAccessToken(options?: any): AxiosPromise<object> {
+        validateAccessToken(options?: any): AxiosPromise<ValidateAccessTokenResponseDto> {
             return localVarFp.validateAccessToken(options).then((request) => request(axios, basePath));
         },
     };
@@ -2994,13 +3062,13 @@ export const DeviceInfoApiAxiosParamCreator = function (configuration?: Configur
         },
         /**
          * 
-         * @param {object} body 
+         * @param {UpdateDeviceInfoDto} updateDeviceInfoDto 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        updateDeviceInfo: async (body: object, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            // verify required parameter 'body' is not null or undefined
-            assertParamExists('updateDeviceInfo', 'body', body)
+        updateDeviceInfo: async (updateDeviceInfoDto: UpdateDeviceInfoDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'updateDeviceInfoDto' is not null or undefined
+            assertParamExists('updateDeviceInfo', 'updateDeviceInfoDto', updateDeviceInfoDto)
             const localVarPath = `/device-info`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@@ -3024,7 +3092,7 @@ export const DeviceInfoApiAxiosParamCreator = function (configuration?: Configur
             setSearchParams(localVarUrlObj, localVarQueryParameter);
             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-            localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration)
+            localVarRequestOptions.data = serializeDataIfNeeded(updateDeviceInfoDto, localVarRequestOptions, configuration)
 
             return {
                 url: toPathString(localVarUrlObj),
@@ -3053,12 +3121,12 @@ export const DeviceInfoApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @param {object} body 
+         * @param {UpdateDeviceInfoDto} updateDeviceInfoDto 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async updateDeviceInfo(body: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
-            const localVarAxiosArgs = await localVarAxiosParamCreator.updateDeviceInfo(body, options);
+        async updateDeviceInfo(updateDeviceInfoDto: UpdateDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.updateDeviceInfo(updateDeviceInfoDto, options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
     }
@@ -3082,12 +3150,12 @@ export const DeviceInfoApiFactory = function (configuration?: Configuration, bas
         },
         /**
          * 
-         * @param {object} body 
+         * @param {UpdateDeviceInfoDto} updateDeviceInfoDto 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        updateDeviceInfo(body: object, options?: any): AxiosPromise<DeviceInfoResponseDto> {
-            return localVarFp.updateDeviceInfo(body, options).then((request) => request(axios, basePath));
+        updateDeviceInfo(updateDeviceInfoDto: UpdateDeviceInfoDto, options?: any): AxiosPromise<DeviceInfoResponseDto> {
+            return localVarFp.updateDeviceInfo(updateDeviceInfoDto, options).then((request) => request(axios, basePath));
         },
     };
 };
@@ -3112,13 +3180,13 @@ export class DeviceInfoApi extends BaseAPI {
 
     /**
      * 
-     * @param {object} body 
+     * @param {UpdateDeviceInfoDto} updateDeviceInfoDto 
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof DeviceInfoApi
      */
-    public updateDeviceInfo(body: object, options?: AxiosRequestConfig) {
-        return DeviceInfoApiFp(this.configuration).updateDeviceInfo(body, options).then((request) => request(this.axios, this.basePath));
+    public updateDeviceInfo(updateDeviceInfoDto: UpdateDeviceInfoDto, options?: AxiosRequestConfig) {
+        return DeviceInfoApiFp(this.configuration).updateDeviceInfo(updateDeviceInfoDto, options).then((request) => request(this.axios, this.basePath));
     }
 }
 
diff --git a/web/src/lib/components/forms/login-form.svelte b/web/src/lib/components/forms/login-form.svelte
index 7b20c4402f..b0934fb5dc 100644
--- a/web/src/lib/components/forms/login-form.svelte
+++ b/web/src/lib/components/forms/login-form.svelte
@@ -45,10 +45,6 @@
 	</div>
 
 	{#if loginPageMessage}
-		<!-- <div class="bg-blue-100 m-4 p-2 border-t border-b border-blue-500 text-blue-700" role="alert">
-			<p>{@html loginPageMessage}</p>
-		</div> -->
-
 		<p class="text-sm border rounded-md m-4 p-4 text-immich-primary font-medium bg-immich-primary/5">
 			{@html loginPageMessage}
 		</p>