diff --git a/lib/appwrite.dart b/lib/appwrite.dart
index 68c1d899..35f90d27 100644
--- a/lib/appwrite.dart
+++ b/lib/appwrite.dart
@@ -6,6 +6,8 @@ import 'dart:math';
 import 'dart:typed_data';
 
 import 'models.dart' as models;
+import 'src/call_handlers/offline_call_handler.dart';
+import 'src/call_params.dart';
 import 'src/enums.dart';
 import 'src/input_file.dart';
 import 'src/service.dart';
diff --git a/lib/query.dart b/lib/query.dart
index 6dcca4a4..ab361b1c 100644
--- a/lib/query.dart
+++ b/lib/query.dart
@@ -47,8 +47,8 @@ class Query {
   static String parseValues(dynamic value) =>
       (value is String) ? '"$value"' : '$value';
 
-  String method;
-  List<dynamic> params;
+  final String method;
+  final List<dynamic> params;
 
   factory Query.parse(String query) {
     if (!query.contains('(') || !query.contains(')')) {
diff --git a/lib/services/account.dart b/lib/services/account.dart
index c58831b8..d5597a2b 100644
--- a/lib/services/account.dart
+++ b/lib/services/account.dart
@@ -1,1165 +1,901 @@
 part of appwrite;
 
-    /// The Account service allows you to authenticate and manage a user account.
+/// The Account service allows you to authenticate and manage a user account.
 class Account extends Service {
-    Account(super.client);
-
-    /// Get Account
-    ///
-    /// Get currently logged in user data as JSON object.
-    ///
-    Future<models.Account> get() async {
-        const String path = '/account';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Create Account
-    ///
-    /// Use this endpoint to allow a new user to register a new account in your
-    /// project. After the user registration completes successfully, you can use
-    /// the [/account/verfication](/docs/client/account#accountCreateVerification)
-    /// route to start verifying the user email address. To allow the new user to
-    /// login to their new account, you need to create a new [account
-    /// session](/docs/client/account#accountCreateSession).
-    ///
-    Future<models.Account> create({required String userId, required String email, required String password, String? name}) async {
-        const String path = '/account';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'email': email,
-            'password': password,
-            'name': name,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Update Email
-    ///
-    /// Update currently logged in user account email address. After changing user
-    /// address, the user confirmation status will get reset. A new confirmation
-    /// email is not sent automatically however you can use the send confirmation
-    /// email endpoint again to send the confirmation email. For security measures,
-    /// user password is required to complete this request.
-    /// This endpoint can also be used to convert an anonymous account to a normal
-    /// one, by passing an email address and a new password.
-    /// 
-    ///
-    Future<models.Account> updateEmail({required String email, required String password}) async {
-        const String path = '/account/email';
-
-        final Map<String, dynamic> params = {
-            'email': email,
-            'password': password,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Create JWT
-    ///
-    /// Use this endpoint to create a JSON Web Token. You can use the resulting JWT
-    /// to authenticate on behalf of the current user when working with the
-    /// Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes
-    /// from its creation and will be invalid if the user will logout in that time
-    /// frame.
-    ///
-    Future<models.Jwt> createJWT() async {
-        const String path = '/account/jwt';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Jwt.fromMap(res.data);
-
-    }
-
-    /// List Logs
-    ///
-    /// Get currently logged in user list of latest security activity logs. Each
-    /// log returns user IP address, location and date and time of log.
-    ///
-    Future<models.LogList> listLogs({List<String>? queries}) async {
-        const String path = '/account/logs';
-
-        final Map<String, dynamic> params = {
-            'queries': queries,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = 'logs';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.LogList.fromMap(res.data);
-
-    }
-
-    /// Update Name
-    ///
-    /// Update currently logged in user account name.
-    ///
-    Future<models.Account> updateName({required String name}) async {
-        const String path = '/account/name';
-
-        final Map<String, dynamic> params = {
-            'name': name,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Update Password
-    ///
-    /// Update currently logged in user password. For validation, user is required
-    /// to pass in the new password, and the old password. For users created with
-    /// OAuth, Team Invites and Magic URL, oldPassword is optional.
-    ///
-    Future<models.Account> updatePassword({required String password, String? oldPassword}) async {
-        const String path = '/account/password';
-
-        final Map<String, dynamic> params = {
-            'password': password,
-            'oldPassword': oldPassword,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Update Phone
-    ///
-    /// Update the currently logged in user's phone number. After updating the
-    /// phone number, the phone verification status will be reset. A confirmation
-    /// SMS is not sent automatically, however you can use the [POST
-    /// /account/verification/phone](/docs/client/account#accountCreatePhoneVerification)
-    /// endpoint to send a confirmation SMS.
-    ///
-    Future<models.Account> updatePhone({required String phone, required String password}) async {
-        const String path = '/account/phone';
-
-        final Map<String, dynamic> params = {
-            'phone': phone,
-            'password': password,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Get Account Preferences
-    ///
-    /// Get currently logged in user preferences as a key-value object.
-    ///
-    Future<models.Preferences> getPrefs() async {
-        const String path = '/account/prefs';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account/prefs';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Preferences.fromMap(res.data);
-
-    }
-
-    /// Update Preferences
-    ///
-    /// Update currently logged in user account preferences. The object you pass is
-    /// stored as is, and replaces any previous value. The maximum allowed prefs
-    /// size is 64kB and throws error if exceeded.
-    ///
-    Future<models.Account> updatePrefs({required Map prefs}) async {
-        const String path = '/account/prefs';
-
-        final Map<String, dynamic> params = {
-            'prefs': prefs,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account/prefs';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Create Password Recovery
-    ///
-    /// Sends the user an email with a temporary secret key for password reset.
-    /// When the user clicks the confirmation link he is redirected back to your
-    /// app password reset URL with the secret key and email address values
-    /// attached to the URL query string. Use the query string params to submit a
-    /// request to the [PUT
-    /// /account/recovery](/docs/client/account#accountUpdateRecovery) endpoint to
-    /// complete the process. The verification link sent to the user's email
-    /// address is valid for 1 hour.
-    ///
-    Future<models.Token> createRecovery({required String email, required String url}) async {
-        const String path = '/account/recovery';
-
-        final Map<String, dynamic> params = {
-            'email': email,
-            'url': url,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-
-    /// Create Password Recovery (confirmation)
-    ///
-    /// Use this endpoint to complete the user account password reset. Both the
-    /// **userId** and **secret** arguments will be passed as query parameters to
-    /// the redirect URL you have provided when sending your request to the [POST
-    /// /account/recovery](/docs/client/account#accountCreateRecovery) endpoint.
-    /// 
-    /// Please note that in order to avoid a [Redirect
-    /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
-    /// the only valid redirect URLs are the ones from domains you have set when
-    /// adding your platforms in the console interface.
-    ///
-    Future<models.Token> updateRecovery({required String userId, required String secret, required String password, required String passwordAgain}) async {
-        const String path = '/account/recovery';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'secret': secret,
-            'password': password,
-            'passwordAgain': passwordAgain,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.put,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-
-    /// List Sessions
-    ///
-    /// Get currently logged in user list of active sessions across different
-    /// devices.
-    ///
-    Future<models.SessionList> listSessions() async {
-        const String path = '/account/sessions';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account/sessions';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = 'sessions';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.SessionList.fromMap(res.data);
-
-    }
-
-    /// Delete Sessions
-    ///
-    /// Delete all sessions from the user account and remove any sessions cookies
-    /// from the end client.
-    ///
-    Future deleteSessions() async {
-        const String path = '/account/sessions';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.delete,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-
-    /// Create Anonymous Session
-    ///
-    /// Use this endpoint to allow a new user to register an anonymous account in
-    /// your project. This route will also create a new session for the user. To
-    /// allow the new user to convert an anonymous account to a normal account, you
-    /// need to update its [email and
-    /// password](/docs/client/account#accountUpdateEmail) or create an [OAuth2
-    /// session](/docs/client/account#accountCreateOAuth2Session).
-    ///
-    Future<models.Session> createAnonymousSession() async {
-        const String path = '/account/sessions/anonymous';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Session.fromMap(res.data);
-
-    }
-
-    /// Create Email Session
-    ///
-    /// Allow the user to login into their account by providing a valid email and
-    /// password combination. This route will create a new session for the user.
-    /// 
-    /// A user is limited to 10 active sessions at a time by default. [Learn more
-    /// about session limits](/docs/authentication#limits).
-    ///
-    Future<models.Session> createEmailSession({required String email, required String password}) async {
-        const String path = '/account/sessions/email';
-
-        final Map<String, dynamic> params = {
-            'email': email,
-            'password': password,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Session.fromMap(res.data);
-
-    }
-
-    /// Create Magic URL session
-    ///
-    /// Sends the user an email with a secret key for creating a session. If the
-    /// provided user ID has not be registered, a new user will be created. When
-    /// the user clicks the link in the email, the user is redirected back to the
-    /// URL you provided with the secret key and userId values attached to the URL
-    /// query string. Use the query string parameters to submit a request to the
-    /// [PUT
-    /// /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession)
-    /// endpoint to complete the login process. The link sent to the user's email
-    /// address is valid for 1 hour. If you are on a mobile device you can leave
-    /// the URL parameter empty, so that the login completion will be handled by
-    /// your Appwrite instance by default.
-    /// 
-    /// A user is limited to 10 active sessions at a time by default. [Learn more
-    /// about session limits](/docs/authentication#limits).
-    ///
-    Future<models.Token> createMagicURLSession({required String userId, required String email, String? url}) async {
-        const String path = '/account/sessions/magic-url';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'email': email,
-            'url': url,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-
-    /// Create Magic URL session (confirmation)
-    ///
-    /// Use this endpoint to complete creating the session with the Magic URL. Both
-    /// the **userId** and **secret** arguments will be passed as query parameters
-    /// to the redirect URL you have provided when sending your request to the
-    /// [POST
-    /// /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession)
-    /// endpoint.
-    /// 
-    /// Please note that in order to avoid a [Redirect
-    /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
-    /// the only valid redirect URLs are the ones from domains you have set when
-    /// adding your platforms in the console interface.
-    ///
-    Future<models.Session> updateMagicURLSession({required String userId, required String secret}) async {
-        const String path = '/account/sessions/magic-url';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'secret': secret,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.put,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Session.fromMap(res.data);
-
-    }
-
-    /// Create OAuth2 Session
-    ///
-    /// Allow the user to login to their account using the OAuth2 provider of their
-    /// choice. Each OAuth2 provider should be enabled from the Appwrite console
-    /// first. Use the success and failure arguments to provide a redirect URL's
-    /// back to your app when login is completed.
-    /// 
-    /// If there is already an active session, the new session will be attached to
-    /// the logged-in account. If there are no active sessions, the server will
-    /// attempt to look for a user with the same email address as the email
-    /// received from the OAuth2 provider and attach the new session to the
-    /// existing user. If no matching user is found - the server will create a new
-    /// user.
-    /// 
-    /// A user is limited to 10 active sessions at a time by default. [Learn more
-    /// about session limits](/docs/authentication#limits).
-    /// 
-    ///
-    Future createOAuth2Session({required String provider, String? success, String? failure, List<String>? scopes}) async {
-        final String path = '/account/sessions/oauth2/{provider}'.replaceAll('{provider}', provider);
-
-        final Map<String, dynamic> params = {
-            
-            'success': success,
-            'failure': failure,
-            'scopes': scopes,
-            
-            'project': client.config['project'],
-        };
-
-        final List query = [];
-
-        params.forEach((key, value) {
-          if (value is List) { 
-            for (var item in value) {
-              query.add(Uri.encodeComponent(key + '[]') + '=' + Uri.encodeComponent(item));
-            }
-          } else if(value != null) {
-              query.add(Uri.encodeComponent(key) + '=' + Uri.encodeComponent(value));
-          }
-        });
-
-        Uri endpoint = Uri.parse(client.endPoint);
-        Uri url = Uri(scheme: endpoint.scheme,
-          host: endpoint.host,
-          port: endpoint.port,
-          path: endpoint.path + path,
-          query: query.join('&')
-        );
-
-      return client.webAuth(url, callbackUrlScheme: success);
-    }
-
-    /// Create Phone session
-    ///
-    /// Sends the user an SMS with a secret key for creating a session. If the
-    /// provided user ID has not be registered, a new user will be created. Use the
-    /// returned user ID and secret and submit a request to the [PUT
-    /// /account/sessions/phone](/docs/client/account#accountUpdatePhoneSession)
-    /// endpoint to complete the login process. The secret sent to the user's phone
-    /// is valid for 15 minutes.
-    /// 
-    /// A user is limited to 10 active sessions at a time by default. [Learn more
-    /// about session limits](/docs/authentication#limits).
-    ///
-    Future<models.Token> createPhoneSession({required String userId, required String phone}) async {
-        const String path = '/account/sessions/phone';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'phone': phone,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-
-    /// Create Phone Session (confirmation)
-    ///
-    /// Use this endpoint to complete creating a session with SMS. Use the
-    /// **userId** from the
-    /// [createPhoneSession](/docs/client/account#accountCreatePhoneSession)
-    /// endpoint and the **secret** received via SMS to successfully update and
-    /// confirm the phone session.
-    ///
-    Future<models.Session> updatePhoneSession({required String userId, required String secret}) async {
-        const String path = '/account/sessions/phone';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'secret': secret,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.put,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Session.fromMap(res.data);
-
-    }
-
-    /// Get Session
-    ///
-    /// Use this endpoint to get a logged in user's session using a Session ID.
-    /// Inputting 'current' will return the current session being used.
-    ///
-    Future<models.Session> getSession({required String sessionId}) async {
-        final String path = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/account/sessions'.replaceAll('{sessionId}', sessionId);
-        final cacheKey = sessionId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Session.fromMap(res.data);
-
-    }
-
-    /// Update OAuth Session (Refresh Tokens)
-    ///
-    /// Access tokens have limited lifespan and expire to mitigate security risks.
-    /// If session was created using an OAuth provider, this route can be used to
-    /// "refresh" the access token.
-    ///
-    Future<models.Session> updateSession({required String sessionId}) async {
-        final String path = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Session.fromMap(res.data);
-
-    }
-
-    /// Delete Session
-    ///
-    /// Use this endpoint to log out the currently logged in user from all their
-    /// account sessions across all of their different devices. When using the
-    /// Session ID argument, only the unique session ID provided is deleted.
-    /// 
-    ///
-    Future deleteSession({required String sessionId}) async {
-        final String path = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.delete,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-
-    /// Update Status
-    ///
-    /// Block the currently logged in user account. Behind the scene, the user
-    /// record is not deleted but permanently blocked from any access. To
-    /// completely delete a user, use the Users API instead.
-    ///
-    Future<models.Account> updateStatus() async {
-        const String path = '/account/status';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Account.fromMap(res.data);
-
-    }
-
-    /// Create Email Verification
-    ///
-    /// Use this endpoint to send a verification message to your user email address
-    /// to confirm they are the valid owners of that address. Both the **userId**
-    /// and **secret** arguments will be passed as query parameters to the URL you
-    /// have provided to be attached to the verification email. The provided URL
-    /// should redirect the user back to your app and allow you to complete the
-    /// verification process by verifying both the **userId** and **secret**
-    /// parameters. Learn more about how to [complete the verification
-    /// process](/docs/client/account#accountUpdateEmailVerification). The
-    /// verification link sent to the user's email address is valid for 7 days.
-    /// 
-    /// Please note that in order to avoid a [Redirect
-    /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md),
-    /// the only valid redirect URLs are the ones from domains you have set when
-    /// adding your platforms in the console interface.
-    /// 
-    ///
-    Future<models.Token> createVerification({required String url}) async {
-        const String path = '/account/verification';
-
-        final Map<String, dynamic> params = {
-            'url': url,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-
-    /// Create Email Verification (confirmation)
-    ///
-    /// Use this endpoint to complete the user email verification process. Use both
-    /// the **userId** and **secret** parameters that were attached to your app URL
-    /// to verify the user email ownership. If confirmed this route will return a
-    /// 200 status code.
-    ///
-    Future<models.Token> updateVerification({required String userId, required String secret}) async {
-        const String path = '/account/verification';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'secret': secret,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.put,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-
-    /// Create Phone Verification
-    ///
-    /// Use this endpoint to send a verification SMS to the currently logged in
-    /// user. This endpoint is meant for use after updating a user's phone number
-    /// using the [accountUpdatePhone](/docs/client/account#accountUpdatePhone)
-    /// endpoint. Learn more about how to [complete the verification
-    /// process](/docs/client/account#accountUpdatePhoneVerification). The
-    /// verification code sent to the user's phone number is valid for 15 minutes.
-    ///
-    Future<models.Token> createPhoneVerification() async {
-        const String path = '/account/verification/phone';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-
-    /// Create Phone Verification (confirmation)
-    ///
-    /// Use this endpoint to complete the user phone verification process. Use the
-    /// **userId** and **secret** that were sent to your user's phone number to
-    /// verify the user email ownership. If confirmed this route will return a 200
-    /// status code.
-    ///
-    Future<models.Token> updatePhoneVerification({required String userId, required String secret}) async {
-        const String path = '/account/verification/phone';
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'secret': secret,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.put,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Token.fromMap(res.data);
-
-    }
-}
\ No newline at end of file
+  Account(super.client);
+
+  /// Get Account
+  ///
+  /// Get currently logged in user data as JSON object.
+  ///
+  Future<models.Account> get() async {
+    const String path = '/account';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Create Account
+  ///
+  /// Use this endpoint to allow a new user to register a new account in your
+  /// project. After the user registration completes successfully, you can use
+  /// the [/account/verfication](/docs/client/account#accountCreateVerification)
+  /// route to start verifying the user email address. To allow the new user to
+  /// login to their new account, you need to create a new [account
+  /// session](/docs/client/account#accountCreateSession).
+  ///
+  Future<models.Account> create(
+      {required String userId,
+      required String email,
+      required String password,
+      String? name}) async {
+    const String path = '/account';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'email': email,
+      'password': password,
+      'name': name,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Update Email
+  ///
+  /// Update currently logged in user account email address. After changing user
+  /// address, the user confirmation status will get reset. A new confirmation
+  /// email is not sent automatically however you can use the send confirmation
+  /// email endpoint again to send the confirmation email. For security measures,
+  /// user password is required to complete this request.
+  /// This endpoint can also be used to convert an anonymous account to a normal
+  /// one, by passing an email address and a new password.
+  ///
+  ///
+  Future<models.Account> updateEmail(
+      {required String email, required String password}) async {
+    const String path = '/account/email';
+
+    final Map<String, dynamic> params = {
+      'email': email,
+      'password': password,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Create JWT
+  ///
+  /// Use this endpoint to create a JSON Web Token. You can use the resulting JWT
+  /// to authenticate on behalf of the current user when working with the
+  /// Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes
+  /// from its creation and will be invalid if the user will logout in that time
+  /// frame.
+  ///
+  Future<models.Jwt> createJWT() async {
+    const String path = '/account/jwt';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Jwt.fromMap(res.data);
+  }
+
+  /// List Logs
+  ///
+  /// Get currently logged in user list of latest security activity logs. Each
+  /// log returns user IP address, location and date and time of log.
+  ///
+  Future<models.LogList> listLogs({List<String>? queries}) async {
+    const String path = '/account/logs';
+
+    final Map<String, dynamic> params = {
+      'queries': queries,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.LogList.fromMap(res.data);
+  }
+
+  /// Update Name
+  ///
+  /// Update currently logged in user account name.
+  ///
+  Future<models.Account> updateName({required String name}) async {
+    const String path = '/account/name';
+
+    final Map<String, dynamic> params = {
+      'name': name,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Update Password
+  ///
+  /// Update currently logged in user password. For validation, user is required
+  /// to pass in the new password, and the old password. For users created with
+  /// OAuth, Team Invites and Magic URL, oldPassword is optional.
+  ///
+  Future<models.Account> updatePassword(
+      {required String password, String? oldPassword}) async {
+    const String path = '/account/password';
+
+    final Map<String, dynamic> params = {
+      'password': password,
+      'oldPassword': oldPassword,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Update Phone
+  ///
+  /// Update the currently logged in user's phone number. After updating the
+  /// phone number, the phone verification status will be reset. A confirmation
+  /// SMS is not sent automatically, however you can use the [POST
+  /// /account/verification/phone](/docs/client/account#accountCreatePhoneVerification)
+  /// endpoint to send a confirmation SMS.
+  ///
+  Future<models.Account> updatePhone(
+      {required String phone, required String password}) async {
+    const String path = '/account/phone';
+
+    final Map<String, dynamic> params = {
+      'phone': phone,
+      'password': password,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Get Account Preferences
+  ///
+  /// Get currently logged in user preferences as a key-value object.
+  ///
+  Future<models.Preferences> getPrefs() async {
+    const String path = '/account/prefs';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Preferences.fromMap(res.data);
+  }
+
+  /// Update Preferences
+  ///
+  /// Update currently logged in user account preferences. The object you pass is
+  /// stored as is, and replaces any previous value. The maximum allowed prefs
+  /// size is 64kB and throws error if exceeded.
+  ///
+  Future<models.Account> updatePrefs({required Map prefs}) async {
+    const String path = '/account/prefs';
+
+    final Map<String, dynamic> params = {
+      'prefs': prefs,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Create Password Recovery
+  ///
+  /// Sends the user an email with a temporary secret key for password reset.
+  /// When the user clicks the confirmation link he is redirected back to your
+  /// app password reset URL with the secret key and email address values
+  /// attached to the URL query string. Use the query string params to submit a
+  /// request to the [PUT
+  /// /account/recovery](/docs/client/account#accountUpdateRecovery) endpoint to
+  /// complete the process. The verification link sent to the user's email
+  /// address is valid for 1 hour.
+  ///
+  Future<models.Token> createRecovery(
+      {required String email, required String url}) async {
+    const String path = '/account/recovery';
+
+    final Map<String, dynamic> params = {
+      'email': email,
+      'url': url,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+
+  /// Create Password Recovery (confirmation)
+  ///
+  /// Use this endpoint to complete the user account password reset. Both the
+  /// **userId** and **secret** arguments will be passed as query parameters to
+  /// the redirect URL you have provided when sending your request to the [POST
+  /// /account/recovery](/docs/client/account#accountCreateRecovery) endpoint.
+  ///
+  /// Please note that in order to avoid a [Redirect
+  /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
+  /// the only valid redirect URLs are the ones from domains you have set when
+  /// adding your platforms in the console interface.
+  ///
+  Future<models.Token> updateRecovery(
+      {required String userId,
+      required String secret,
+      required String password,
+      required String passwordAgain}) async {
+    const String path = '/account/recovery';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'secret': secret,
+      'password': password,
+      'passwordAgain': passwordAgain,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.put,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+
+  /// List Sessions
+  ///
+  /// Get currently logged in user list of active sessions across different
+  /// devices.
+  ///
+  Future<models.SessionList> listSessions() async {
+    const String path = '/account/sessions';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.SessionList.fromMap(res.data);
+  }
+
+  /// Delete Sessions
+  ///
+  /// Delete all sessions from the user account and remove any sessions cookies
+  /// from the end client.
+  ///
+  Future deleteSessions() async {
+    const String path = '/account/sessions';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.delete,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+
+  /// Create Anonymous Session
+  ///
+  /// Use this endpoint to allow a new user to register an anonymous account in
+  /// your project. This route will also create a new session for the user. To
+  /// allow the new user to convert an anonymous account to a normal account, you
+  /// need to update its [email and
+  /// password](/docs/client/account#accountUpdateEmail) or create an [OAuth2
+  /// session](/docs/client/account#accountCreateOAuth2Session).
+  ///
+  Future<models.Session> createAnonymousSession() async {
+    const String path = '/account/sessions/anonymous';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Session.fromMap(res.data);
+  }
+
+  /// Create Email Session
+  ///
+  /// Allow the user to login into their account by providing a valid email and
+  /// password combination. This route will create a new session for the user.
+  ///
+  /// A user is limited to 10 active sessions at a time by default. [Learn more
+  /// about session limits](/docs/authentication#limits).
+  ///
+  Future<models.Session> createEmailSession(
+      {required String email, required String password}) async {
+    const String path = '/account/sessions/email';
+
+    final Map<String, dynamic> params = {
+      'email': email,
+      'password': password,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Session.fromMap(res.data);
+  }
+
+  /// Create Magic URL session
+  ///
+  /// Sends the user an email with a secret key for creating a session. If the
+  /// provided user ID has not be registered, a new user will be created. When
+  /// the user clicks the link in the email, the user is redirected back to the
+  /// URL you provided with the secret key and userId values attached to the URL
+  /// query string. Use the query string parameters to submit a request to the
+  /// [PUT
+  /// /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession)
+  /// endpoint to complete the login process. The link sent to the user's email
+  /// address is valid for 1 hour. If you are on a mobile device you can leave
+  /// the URL parameter empty, so that the login completion will be handled by
+  /// your Appwrite instance by default.
+  ///
+  /// A user is limited to 10 active sessions at a time by default. [Learn more
+  /// about session limits](/docs/authentication#limits).
+  ///
+  Future<models.Token> createMagicURLSession(
+      {required String userId, required String email, String? url}) async {
+    const String path = '/account/sessions/magic-url';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'email': email,
+      'url': url,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+
+  /// Create Magic URL session (confirmation)
+  ///
+  /// Use this endpoint to complete creating the session with the Magic URL. Both
+  /// the **userId** and **secret** arguments will be passed as query parameters
+  /// to the redirect URL you have provided when sending your request to the
+  /// [POST
+  /// /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession)
+  /// endpoint.
+  ///
+  /// Please note that in order to avoid a [Redirect
+  /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
+  /// the only valid redirect URLs are the ones from domains you have set when
+  /// adding your platforms in the console interface.
+  ///
+  Future<models.Session> updateMagicURLSession(
+      {required String userId, required String secret}) async {
+    const String path = '/account/sessions/magic-url';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'secret': secret,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.put,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Session.fromMap(res.data);
+  }
+
+  /// Create OAuth2 Session
+  ///
+  /// Allow the user to login to their account using the OAuth2 provider of their
+  /// choice. Each OAuth2 provider should be enabled from the Appwrite console
+  /// first. Use the success and failure arguments to provide a redirect URL's
+  /// back to your app when login is completed.
+  ///
+  /// If there is already an active session, the new session will be attached to
+  /// the logged-in account. If there are no active sessions, the server will
+  /// attempt to look for a user with the same email address as the email
+  /// received from the OAuth2 provider and attach the new session to the
+  /// existing user. If no matching user is found - the server will create a new
+  /// user.
+  ///
+  /// A user is limited to 10 active sessions at a time by default. [Learn more
+  /// about session limits](/docs/authentication#limits).
+  ///
+  ///
+  Future createOAuth2Session(
+      {required String provider,
+      String? success,
+      String? failure,
+      List<String>? scopes}) async {
+    final String path = '/account/sessions/oauth2/{provider}'
+        .replaceAll('{provider}', provider);
+
+    final Map<String, dynamic> params = {
+      'success': success,
+      'failure': failure,
+      'scopes': scopes,
+      'project': client.config['project'],
+    };
+
+    final List query = [];
+
+    params.forEach((key, value) {
+      if (value is List) {
+        for (var item in value) {
+          query.add(Uri.encodeComponent(key + '[]') +
+              '=' +
+              Uri.encodeComponent(item));
+        }
+      } else if (value != null) {
+        query.add(Uri.encodeComponent(key) + '=' + Uri.encodeComponent(value));
+      }
+    });
+
+    Uri endpoint = Uri.parse(client.endPoint);
+    Uri url = Uri(
+        scheme: endpoint.scheme,
+        host: endpoint.host,
+        port: endpoint.port,
+        path: endpoint.path + path,
+        query: query.join('&'));
+
+    return client.webAuth(url, callbackUrlScheme: success);
+  }
+
+  /// Create Phone session
+  ///
+  /// Sends the user an SMS with a secret key for creating a session. If the
+  /// provided user ID has not be registered, a new user will be created. Use the
+  /// returned user ID and secret and submit a request to the [PUT
+  /// /account/sessions/phone](/docs/client/account#accountUpdatePhoneSession)
+  /// endpoint to complete the login process. The secret sent to the user's phone
+  /// is valid for 15 minutes.
+  ///
+  /// A user is limited to 10 active sessions at a time by default. [Learn more
+  /// about session limits](/docs/authentication#limits).
+  ///
+  Future<models.Token> createPhoneSession(
+      {required String userId, required String phone}) async {
+    const String path = '/account/sessions/phone';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'phone': phone,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+
+  /// Create Phone Session (confirmation)
+  ///
+  /// Use this endpoint to complete creating a session with SMS. Use the
+  /// **userId** from the
+  /// [createPhoneSession](/docs/client/account#accountCreatePhoneSession)
+  /// endpoint and the **secret** received via SMS to successfully update and
+  /// confirm the phone session.
+  ///
+  Future<models.Session> updatePhoneSession(
+      {required String userId, required String secret}) async {
+    const String path = '/account/sessions/phone';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'secret': secret,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.put,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Session.fromMap(res.data);
+  }
+
+  /// Get Session
+  ///
+  /// Use this endpoint to get a logged in user's session using a Session ID.
+  /// Inputting 'current' will return the current session being used.
+  ///
+  Future<models.Session> getSession({required String sessionId}) async {
+    final String path =
+        '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Session.fromMap(res.data);
+  }
+
+  /// Update OAuth Session (Refresh Tokens)
+  ///
+  /// Access tokens have limited lifespan and expire to mitigate security risks.
+  /// If session was created using an OAuth provider, this route can be used to
+  /// "refresh" the access token.
+  ///
+  Future<models.Session> updateSession({required String sessionId}) async {
+    final String path =
+        '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Session.fromMap(res.data);
+  }
+
+  /// Delete Session
+  ///
+  /// Use this endpoint to log out the currently logged in user from all their
+  /// account sessions across all of their different devices. When using the
+  /// Session ID argument, only the unique session ID provided is deleted.
+  ///
+  ///
+  Future deleteSession({required String sessionId}) async {
+    final String path =
+        '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.delete,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+
+  /// Update Status
+  ///
+  /// Block the currently logged in user account. Behind the scene, the user
+  /// record is not deleted but permanently blocked from any access. To
+  /// completely delete a user, use the Users API instead.
+  ///
+  Future<models.Account> updateStatus() async {
+    const String path = '/account/status';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Account.fromMap(res.data);
+  }
+
+  /// Create Email Verification
+  ///
+  /// Use this endpoint to send a verification message to your user email address
+  /// to confirm they are the valid owners of that address. Both the **userId**
+  /// and **secret** arguments will be passed as query parameters to the URL you
+  /// have provided to be attached to the verification email. The provided URL
+  /// should redirect the user back to your app and allow you to complete the
+  /// verification process by verifying both the **userId** and **secret**
+  /// parameters. Learn more about how to [complete the verification
+  /// process](/docs/client/account#accountUpdateEmailVerification). The
+  /// verification link sent to the user's email address is valid for 7 days.
+  ///
+  /// Please note that in order to avoid a [Redirect
+  /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md),
+  /// the only valid redirect URLs are the ones from domains you have set when
+  /// adding your platforms in the console interface.
+  ///
+  ///
+  Future<models.Token> createVerification({required String url}) async {
+    const String path = '/account/verification';
+
+    final Map<String, dynamic> params = {
+      'url': url,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+
+  /// Create Email Verification (confirmation)
+  ///
+  /// Use this endpoint to complete the user email verification process. Use both
+  /// the **userId** and **secret** parameters that were attached to your app URL
+  /// to verify the user email ownership. If confirmed this route will return a
+  /// 200 status code.
+  ///
+  Future<models.Token> updateVerification(
+      {required String userId, required String secret}) async {
+    const String path = '/account/verification';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'secret': secret,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.put,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+
+  /// Create Phone Verification
+  ///
+  /// Use this endpoint to send a verification SMS to the currently logged in
+  /// user. This endpoint is meant for use after updating a user's phone number
+  /// using the [accountUpdatePhone](/docs/client/account#accountUpdatePhone)
+  /// endpoint. Learn more about how to [complete the verification
+  /// process](/docs/client/account#accountUpdatePhoneVerification). The
+  /// verification code sent to the user's phone number is valid for 15 minutes.
+  ///
+  Future<models.Token> createPhoneVerification() async {
+    const String path = '/account/verification/phone';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+
+  /// Create Phone Verification (confirmation)
+  ///
+  /// Use this endpoint to complete the user phone verification process. Use the
+  /// **userId** and **secret** that were sent to your user's phone number to
+  /// verify the user email ownership. If confirmed this route will return a 200
+  /// status code.
+  ///
+  Future<models.Token> updatePhoneVerification(
+      {required String userId, required String secret}) async {
+    const String path = '/account/verification/phone';
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'secret': secret,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.put,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Token.fromMap(res.data);
+  }
+}
diff --git a/lib/services/avatars.dart b/lib/services/avatars.dart
index 6962ff5f..3d35e9fa 100644
--- a/lib/services/avatars.dart
+++ b/lib/services/avatars.dart
@@ -1,200 +1,228 @@
 part of appwrite;
 
-    /// The Avatars service aims to help you complete everyday tasks related to
-    /// your app image, icons, and avatars.
+/// The Avatars service aims to help you complete everyday tasks related to
+/// your app image, icons, and avatars.
 class Avatars extends Service {
-    Avatars(super.client);
-
-    /// Get Browser Icon
-    ///
-    /// You can use this endpoint to show different browser icons to your users.
-    /// The code argument receives the browser code as it appears in your user [GET
-    /// /account/sessions](/docs/client/account#accountGetSessions) endpoint. Use
-    /// width, height and quality arguments to change the output settings.
-    /// 
-    /// When one dimension is specified and the other is 0, the image is scaled
-    /// with preserved aspect ratio. If both dimensions are 0, the API provides an
-    /// image at source quality. If dimensions are not specified, the default size
-    /// of image returned is 100x100px.
-    ///
-    Future<Uint8List> getBrowser({required String code, int? width, int? height, int? quality}) async {
-        final String path = '/avatars/browsers/{code}'.replaceAll('{code}', code);
-
-        final Map<String, dynamic> params = {
-            
-            'width': width,
-            'height': height,
-            'quality': quality,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get Credit Card Icon
-    ///
-    /// The credit card endpoint will return you the icon of the credit card
-    /// provider you need. Use width, height and quality arguments to change the
-    /// output settings.
-    /// 
-    /// When one dimension is specified and the other is 0, the image is scaled
-    /// with preserved aspect ratio. If both dimensions are 0, the API provides an
-    /// image at source quality. If dimensions are not specified, the default size
-    /// of image returned is 100x100px.
-    /// 
-    ///
-    Future<Uint8List> getCreditCard({required String code, int? width, int? height, int? quality}) async {
-        final String path = '/avatars/credit-cards/{code}'.replaceAll('{code}', code);
-
-        final Map<String, dynamic> params = {
-            
-            'width': width,
-            'height': height,
-            'quality': quality,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get Favicon
-    ///
-    /// Use this endpoint to fetch the favorite icon (AKA favicon) of any remote
-    /// website URL.
-    /// 
-    ///
-    Future<Uint8List> getFavicon({required String url}) async {
-        const String path = '/avatars/favicon';
-
-        final Map<String, dynamic> params = {
-            
-            'url': url,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get Country Flag
-    ///
-    /// You can use this endpoint to show different country flags icons to your
-    /// users. The code argument receives the 2 letter country code. Use width,
-    /// height and quality arguments to change the output settings. Country codes
-    /// follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
-    /// 
-    /// When one dimension is specified and the other is 0, the image is scaled
-    /// with preserved aspect ratio. If both dimensions are 0, the API provides an
-    /// image at source quality. If dimensions are not specified, the default size
-    /// of image returned is 100x100px.
-    /// 
-    ///
-    Future<Uint8List> getFlag({required String code, int? width, int? height, int? quality}) async {
-        final String path = '/avatars/flags/{code}'.replaceAll('{code}', code);
-
-        final Map<String, dynamic> params = {
-            
-            'width': width,
-            'height': height,
-            'quality': quality,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get Image from URL
-    ///
-    /// Use this endpoint to fetch a remote image URL and crop it to any image size
-    /// you want. This endpoint is very useful if you need to crop and display
-    /// remote images in your app or in case you want to make sure a 3rd party
-    /// image is properly served using a TLS protocol.
-    /// 
-    /// When one dimension is specified and the other is 0, the image is scaled
-    /// with preserved aspect ratio. If both dimensions are 0, the API provides an
-    /// image at source quality. If dimensions are not specified, the default size
-    /// of image returned is 400x400px.
-    /// 
-    ///
-    Future<Uint8List> getImage({required String url, int? width, int? height}) async {
-        const String path = '/avatars/image';
-
-        final Map<String, dynamic> params = {
-            
-            'url': url,
-            'width': width,
-            'height': height,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get User Initials
-    ///
-    /// Use this endpoint to show your user initials avatar icon on your website or
-    /// app. By default, this route will try to print your logged-in user name or
-    /// email initials. You can also overwrite the user name if you pass the 'name'
-    /// parameter. If no name is given and no user is logged, an empty avatar will
-    /// be returned.
-    /// 
-    /// You can use the color and background params to change the avatar colors. By
-    /// default, a random theme will be selected. The random theme will persist for
-    /// the user's initials when reloading the same theme will always return for
-    /// the same initials.
-    /// 
-    /// When one dimension is specified and the other is 0, the image is scaled
-    /// with preserved aspect ratio. If both dimensions are 0, the API provides an
-    /// image at source quality. If dimensions are not specified, the default size
-    /// of image returned is 100x100px.
-    /// 
-    ///
-    Future<Uint8List> getInitials({String? name, int? width, int? height, String? background}) async {
-        const String path = '/avatars/initials';
-
-        final Map<String, dynamic> params = {
-            
-            'name': name,
-            'width': width,
-            'height': height,
-            'background': background,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get QR Code
-    ///
-    /// Converts a given plain text to a QR code image. You can use the query
-    /// parameters to change the size and style of the resulting image.
-    /// 
-    ///
-    Future<Uint8List> getQR({required String text, int? size, int? margin, bool? download}) async {
-        const String path = '/avatars/qr';
-
-        final Map<String, dynamic> params = {
-            
-            'text': text,
-            'size': size,
-            'margin': margin,
-            'download': download,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-}
\ No newline at end of file
+  Avatars(super.client);
+
+  /// Get Browser Icon
+  ///
+  /// You can use this endpoint to show different browser icons to your users.
+  /// The code argument receives the browser code as it appears in your user [GET
+  /// /account/sessions](/docs/client/account#accountGetSessions) endpoint. Use
+  /// width, height and quality arguments to change the output settings.
+  ///
+  /// When one dimension is specified and the other is 0, the image is scaled
+  /// with preserved aspect ratio. If both dimensions are 0, the API provides an
+  /// image at source quality. If dimensions are not specified, the default size
+  /// of image returned is 100x100px.
+  ///
+  Future<Uint8List> getBrowser(
+      {required String code, int? width, int? height, int? quality}) async {
+    final String path = '/avatars/browsers/{code}'.replaceAll('{code}', code);
+
+    final Map<String, dynamic> params = {
+      'width': width,
+      'height': height,
+      'quality': quality,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get Credit Card Icon
+  ///
+  /// The credit card endpoint will return you the icon of the credit card
+  /// provider you need. Use width, height and quality arguments to change the
+  /// output settings.
+  ///
+  /// When one dimension is specified and the other is 0, the image is scaled
+  /// with preserved aspect ratio. If both dimensions are 0, the API provides an
+  /// image at source quality. If dimensions are not specified, the default size
+  /// of image returned is 100x100px.
+  ///
+  ///
+  Future<Uint8List> getCreditCard(
+      {required String code, int? width, int? height, int? quality}) async {
+    final String path =
+        '/avatars/credit-cards/{code}'.replaceAll('{code}', code);
+
+    final Map<String, dynamic> params = {
+      'width': width,
+      'height': height,
+      'quality': quality,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get Favicon
+  ///
+  /// Use this endpoint to fetch the favorite icon (AKA favicon) of any remote
+  /// website URL.
+  ///
+  ///
+  Future<Uint8List> getFavicon({required String url}) async {
+    const String path = '/avatars/favicon';
+
+    final Map<String, dynamic> params = {
+      'url': url,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get Country Flag
+  ///
+  /// You can use this endpoint to show different country flags icons to your
+  /// users. The code argument receives the 2 letter country code. Use width,
+  /// height and quality arguments to change the output settings. Country codes
+  /// follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard.
+  ///
+  /// When one dimension is specified and the other is 0, the image is scaled
+  /// with preserved aspect ratio. If both dimensions are 0, the API provides an
+  /// image at source quality. If dimensions are not specified, the default size
+  /// of image returned is 100x100px.
+  ///
+  ///
+  Future<Uint8List> getFlag(
+      {required String code, int? width, int? height, int? quality}) async {
+    final String path = '/avatars/flags/{code}'.replaceAll('{code}', code);
+
+    final Map<String, dynamic> params = {
+      'width': width,
+      'height': height,
+      'quality': quality,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get Image from URL
+  ///
+  /// Use this endpoint to fetch a remote image URL and crop it to any image size
+  /// you want. This endpoint is very useful if you need to crop and display
+  /// remote images in your app or in case you want to make sure a 3rd party
+  /// image is properly served using a TLS protocol.
+  ///
+  /// When one dimension is specified and the other is 0, the image is scaled
+  /// with preserved aspect ratio. If both dimensions are 0, the API provides an
+  /// image at source quality. If dimensions are not specified, the default size
+  /// of image returned is 400x400px.
+  ///
+  ///
+  Future<Uint8List> getImage(
+      {required String url, int? width, int? height}) async {
+    const String path = '/avatars/image';
+
+    final Map<String, dynamic> params = {
+      'url': url,
+      'width': width,
+      'height': height,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get User Initials
+  ///
+  /// Use this endpoint to show your user initials avatar icon on your website or
+  /// app. By default, this route will try to print your logged-in user name or
+  /// email initials. You can also overwrite the user name if you pass the 'name'
+  /// parameter. If no name is given and no user is logged, an empty avatar will
+  /// be returned.
+  ///
+  /// You can use the color and background params to change the avatar colors. By
+  /// default, a random theme will be selected. The random theme will persist for
+  /// the user's initials when reloading the same theme will always return for
+  /// the same initials.
+  ///
+  /// When one dimension is specified and the other is 0, the image is scaled
+  /// with preserved aspect ratio. If both dimensions are 0, the API provides an
+  /// image at source quality. If dimensions are not specified, the default size
+  /// of image returned is 100x100px.
+  ///
+  ///
+  Future<Uint8List> getInitials(
+      {String? name, int? width, int? height, String? background}) async {
+    const String path = '/avatars/initials';
+
+    final Map<String, dynamic> params = {
+      'name': name,
+      'width': width,
+      'height': height,
+      'background': background,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get QR Code
+  ///
+  /// Converts a given plain text to a QR code image. You can use the query
+  /// parameters to change the size and style of the resulting image.
+  ///
+  ///
+  Future<Uint8List> getQR(
+      {required String text, int? size, int? margin, bool? download}) async {
+    const String path = '/avatars/qr';
+
+    final Map<String, dynamic> params = {
+      'text': text,
+      'size': size,
+      'margin': margin,
+      'download': download,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+}
diff --git a/lib/services/databases.dart b/lib/services/databases.dart
index d33661a1..a1c03f3d 100644
--- a/lib/services/databases.dart
+++ b/lib/services/databases.dart
@@ -1,189 +1,174 @@
 part of appwrite;
 
-    /// The Databases service allows you to create structured collections of
-    /// documents, query and filter lists of documents
+/// The Databases service allows you to create structured collections of
+/// documents, query and filter lists of documents
 class Databases extends Service {
-    Databases(super.client);
-
-    /// List Documents
-    ///
-    /// Get a list of all the user's documents in a given collection. You can use
-    /// the query params to filter your results.
-    ///
-    Future<models.DocumentList> listDocuments({required String databaseId, required String collectionId, List<String>? queries}) async {
-        final String path = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId);
-
-        final Map<String, dynamic> params = {
-            'queries': queries,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId);
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = 'documents';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.DocumentList.fromMap(res.data);
-
-    }
-
-    /// Create Document
-    ///
-    /// Create a new Document. Before using this route, you should create a new
-    /// collection resource using either a [server
-    /// integration](/docs/server/databases#databasesCreateCollection) API or
-    /// directly from your database console.
-    ///
-    Future<models.Document> createDocument({required String databaseId, required String collectionId, required String documentId, required Map data, List<String>? permissions}) async {
-        final String path = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId);
-
-        final Map<String, dynamic> params = {
-            'documentId': documentId,
-            'data': data,
-            'permissions': permissions,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId);
-        final cacheKey = documentId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Document.fromMap(res.data);
-
-    }
-
-    /// Get Document
-    ///
-    /// Get a document by its unique ID. This endpoint response returns a JSON
-    /// object with the document data.
-    ///
-    Future<models.Document> getDocument({required String databaseId, required String collectionId, required String documentId}) async {
-        final String path = '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
-        final cacheKey = documentId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Document.fromMap(res.data);
-
-    }
-
-    /// Update Document
-    ///
-    /// Update a document by its unique ID. Using the patch method you can pass
-    /// only specific fields that will get updated.
-    ///
-    Future<models.Document> updateDocument({required String databaseId, required String collectionId, required String documentId, Map? data, List<String>? permissions}) async {
-        final String path = '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
-
-        final Map<String, dynamic> params = {
-            'data': data,
-            'permissions': permissions,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
-        final cacheKey = documentId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Document.fromMap(res.data);
-
-    }
-
-    /// Delete Document
-    ///
-    /// Delete a document by its unique ID.
-    ///
-    Future deleteDocument({required String databaseId, required String collectionId, required String documentId}) async {
-        final String path = '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId);
-        final cacheKey = documentId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.delete,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-}
\ No newline at end of file
+  Databases(super.client);
+
+  /// List Documents
+  ///
+  /// Get a list of all the user's documents in a given collection. You can use
+  /// the query params to filter your results.
+  ///
+  Future<models.DocumentList> listDocuments(
+      {required String databaseId,
+      required String collectionId,
+      List<String>? queries}) async {
+    final String path =
+        '/databases/{databaseId}/collections/{collectionId}/documents'
+            .replaceAll('{databaseId}', databaseId)
+            .replaceAll('{collectionId}', collectionId);
+
+    final Map<String, dynamic> params = {
+      'queries': queries,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.DocumentList.fromMap(res.data);
+  }
+
+  /// Create Document
+  ///
+  /// Create a new Document. Before using this route, you should create a new
+  /// collection resource using either a [server
+  /// integration](/docs/server/databases#databasesCreateCollection) API or
+  /// directly from your database console.
+  ///
+  Future<models.Document> createDocument(
+      {required String databaseId,
+      required String collectionId,
+      required String documentId,
+      required Map data,
+      List<String>? permissions}) async {
+    final String path =
+        '/databases/{databaseId}/collections/{collectionId}/documents'
+            .replaceAll('{databaseId}', databaseId)
+            .replaceAll('{collectionId}', collectionId);
+
+    final Map<String, dynamic> params = {
+      'documentId': documentId,
+      'data': data,
+      'permissions': permissions,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Document.fromMap(res.data);
+  }
+
+  /// Get Document
+  ///
+  /// Get a document by its unique ID. This endpoint response returns a JSON
+  /// object with the document data.
+  ///
+  Future<models.Document> getDocument(
+      {required String databaseId,
+      required String collectionId,
+      required String documentId}) async {
+    final String path =
+        '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'
+            .replaceAll('{databaseId}', databaseId)
+            .replaceAll('{collectionId}', collectionId)
+            .replaceAll('{documentId}', documentId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Document.fromMap(res.data);
+  }
+
+  /// Update Document
+  ///
+  /// Update a document by its unique ID. Using the patch method you can pass
+  /// only specific fields that will get updated.
+  ///
+  Future<models.Document> updateDocument(
+      {required String databaseId,
+      required String collectionId,
+      required String documentId,
+      Map? data,
+      List<String>? permissions}) async {
+    final String path =
+        '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'
+            .replaceAll('{databaseId}', databaseId)
+            .replaceAll('{collectionId}', collectionId)
+            .replaceAll('{documentId}', documentId);
+
+    final Map<String, dynamic> params = {
+      'data': data,
+      'permissions': permissions,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Document.fromMap(res.data);
+  }
+
+  /// Delete Document
+  ///
+  /// Delete a document by its unique ID.
+  ///
+  Future deleteDocument(
+      {required String databaseId,
+      required String collectionId,
+      required String documentId}) async {
+    final String path =
+        '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'
+            .replaceAll('{databaseId}', databaseId)
+            .replaceAll('{collectionId}', collectionId)
+            .replaceAll('{documentId}', documentId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.delete,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+}
diff --git a/lib/services/functions.dart b/lib/services/functions.dart
index 1810f1b5..37afdc2d 100644
--- a/lib/services/functions.dart
+++ b/lib/services/functions.dart
@@ -1,117 +1,95 @@
 part of appwrite;
 
-    /// The Functions Service allows you view, create and manage your Cloud
-    /// Functions.
+/// The Functions Service allows you view, create and manage your Cloud
+/// Functions.
 class Functions extends Service {
-    Functions(super.client);
-
-    /// List Executions
-    ///
-    /// Get a list of all the current user function execution logs. You can use the
-    /// query params to filter your results.
-    ///
-    Future<models.ExecutionList> listExecutions({required String functionId, List<String>? queries, String? search}) async {
-        final String path = '/functions/{functionId}/executions'.replaceAll('{functionId}', functionId);
-
-        final Map<String, dynamic> params = {
-            'queries': queries,
-            'search': search,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = 'executions';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.ExecutionList.fromMap(res.data);
-
-    }
-
-    /// Create Execution
-    ///
-    /// Trigger a function execution. The returned object will return you the
-    /// current execution status. You can ping the `Get Execution` endpoint to get
-    /// updates on the current execution status. Once this endpoint is called, your
-    /// function execution process will start asynchronously.
-    ///
-    Future<models.Execution> createExecution({required String functionId, String? data, bool? xasync}) async {
-        final String path = '/functions/{functionId}/executions'.replaceAll('{functionId}', functionId);
-
-        final Map<String, dynamic> params = {
-            'data': data,
-            'async': xasync,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Execution.fromMap(res.data);
-
-    }
-
-    /// Get Execution
-    ///
-    /// Get a function execution log by its unique ID.
-    ///
-    Future<models.Execution> getExecution({required String functionId, required String executionId}) async {
-        final String path = '/functions/{functionId}/executions/{executionId}'.replaceAll('{functionId}', functionId).replaceAll('{executionId}', executionId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Execution.fromMap(res.data);
-
-    }
-}
\ No newline at end of file
+  Functions(super.client);
+
+  /// List Executions
+  ///
+  /// Get a list of all the current user function execution logs. You can use the
+  /// query params to filter your results.
+  ///
+  Future<models.ExecutionList> listExecutions(
+      {required String functionId,
+      List<String>? queries,
+      String? search}) async {
+    final String path = '/functions/{functionId}/executions'
+        .replaceAll('{functionId}', functionId);
+
+    final Map<String, dynamic> params = {
+      'queries': queries,
+      'search': search,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.ExecutionList.fromMap(res.data);
+  }
+
+  /// Create Execution
+  ///
+  /// Trigger a function execution. The returned object will return you the
+  /// current execution status. You can ping the `Get Execution` endpoint to get
+  /// updates on the current execution status. Once this endpoint is called, your
+  /// function execution process will start asynchronously.
+  ///
+  Future<models.Execution> createExecution(
+      {required String functionId, String? data, bool? xasync}) async {
+    final String path = '/functions/{functionId}/executions'
+        .replaceAll('{functionId}', functionId);
+
+    final Map<String, dynamic> params = {
+      'data': data,
+      'async': xasync,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Execution.fromMap(res.data);
+  }
+
+  /// Get Execution
+  ///
+  /// Get a function execution log by its unique ID.
+  ///
+  Future<models.Execution> getExecution(
+      {required String functionId, required String executionId}) async {
+    final String path = '/functions/{functionId}/executions/{executionId}'
+        .replaceAll('{functionId}', functionId)
+        .replaceAll('{executionId}', executionId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Execution.fromMap(res.data);
+  }
+}
diff --git a/lib/services/graphql.dart b/lib/services/graphql.dart
index 2f92eaf1..96804d88 100644
--- a/lib/services/graphql.dart
+++ b/lib/services/graphql.dart
@@ -1,77 +1,59 @@
 part of appwrite;
 
-    /// The GraphQL API allows you to query and mutate your Appwrite server using
-    /// GraphQL.
+/// The GraphQL API allows you to query and mutate your Appwrite server using
+/// GraphQL.
 class Graphql extends Service {
-    Graphql(super.client);
-
-    /// GraphQL Endpoint
-    ///
-    /// Execute a GraphQL mutation.
-    ///
-    Future query({required Map query}) async {
-        const String path = '/graphql';
-
-        final Map<String, dynamic> params = {
-            'query': query,
-        };
-
-        final Map<String, String> headers = {
-            'x-sdk-graphql': 'true',            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-
-    /// GraphQL Endpoint
-    ///
-    /// Execute a GraphQL mutation.
-    ///
-    Future mutation({required Map query}) async {
-        const String path = '/graphql/mutation';
-
-        final Map<String, dynamic> params = {
-            'query': query,
-        };
-
-        final Map<String, String> headers = {
-            'x-sdk-graphql': 'true',            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-}
\ No newline at end of file
+  Graphql(super.client);
+
+  /// GraphQL Endpoint
+  ///
+  /// Execute a GraphQL mutation.
+  ///
+  Future query({required Map query}) async {
+    const String path = '/graphql';
+
+    final Map<String, dynamic> params = {
+      'query': query,
+    };
+
+    final Map<String, String> headers = {
+      'x-sdk-graphql': 'true',
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+
+  /// GraphQL Endpoint
+  ///
+  /// Execute a GraphQL mutation.
+  ///
+  Future mutation({required Map query}) async {
+    const String path = '/graphql/mutation';
+
+    final Map<String, dynamic> params = {
+      'query': query,
+    };
+
+    final Map<String, String> headers = {
+      'x-sdk-graphql': 'true',
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+}
diff --git a/lib/services/locale.dart b/lib/services/locale.dart
index 2bc60905..14fafba6 100644
--- a/lib/services/locale.dart
+++ b/lib/services/locale.dart
@@ -1,257 +1,180 @@
 part of appwrite;
 
-    /// The Locale service allows you to customize your app based on your users'
-    /// location.
+/// The Locale service allows you to customize your app based on your users'
+/// location.
 class Locale extends Service {
-    Locale(super.client);
-
-    /// Get User Locale
-    ///
-    /// Get the current user location based on IP. Returns an object with user
-    /// country code, country name, continent name, continent code, ip address and
-    /// suggested currency. You can use the locale header to get the data in a
-    /// supported language.
-    /// 
-    /// ([IP Geolocation by DB-IP](https://db-ip.com))
-    ///
-    Future<models.Locale> get() async {
-        const String path = '/locale';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/locale';
-        final cacheKey = 'current';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Locale.fromMap(res.data);
-
-    }
-
-    /// List Continents
-    ///
-    /// List of all continents. You can use the locale header to get the data in a
-    /// supported language.
-    ///
-    Future<models.ContinentList> listContinents() async {
-        const String path = '/locale/continents';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/locale/continents';
-        final cacheKey = '';
-        final cacheResponseIdKey = 'code';
-        final cacheResponseContainerKey = 'continents';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.ContinentList.fromMap(res.data);
-
-    }
-
-    /// List Countries
-    ///
-    /// List of all countries. You can use the locale header to get the data in a
-    /// supported language.
-    ///
-    Future<models.CountryList> listCountries() async {
-        const String path = '/locale/countries';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/locale/countries';
-        final cacheKey = '';
-        final cacheResponseIdKey = 'code';
-        final cacheResponseContainerKey = 'countries';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.CountryList.fromMap(res.data);
-
-    }
-
-    /// List EU Countries
-    ///
-    /// List of all countries that are currently members of the EU. You can use the
-    /// locale header to get the data in a supported language.
-    ///
-    Future<models.CountryList> listCountriesEU() async {
-        const String path = '/locale/countries/eu';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/locale/countries/eu';
-        final cacheKey = '';
-        final cacheResponseIdKey = 'code';
-        final cacheResponseContainerKey = 'countries';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.CountryList.fromMap(res.data);
-
-    }
-
-    /// List Countries Phone Codes
-    ///
-    /// List of all countries phone codes. You can use the locale header to get the
-    /// data in a supported language.
-    ///
-    Future<models.PhoneList> listCountriesPhones() async {
-        const String path = '/locale/countries/phones';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/locale/countries/phones';
-        final cacheKey = '';
-        final cacheResponseIdKey = 'countryCode';
-        final cacheResponseContainerKey = 'phones';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.PhoneList.fromMap(res.data);
-
-    }
-
-    /// List Currencies
-    ///
-    /// List of all currencies, including currency symbol, name, plural, and
-    /// decimal digits for all major and minor currencies. You can use the locale
-    /// header to get the data in a supported language.
-    ///
-    Future<models.CurrencyList> listCurrencies() async {
-        const String path = '/locale/currencies';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/locale/currencies';
-        final cacheKey = '';
-        final cacheResponseIdKey = 'code';
-        final cacheResponseContainerKey = 'currencies';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.CurrencyList.fromMap(res.data);
-
-    }
-
-    /// List Languages
-    ///
-    /// List of all languages classified by ISO 639-1 including 2-letter code, name
-    /// in English, and name in the respective language.
-    ///
-    Future<models.LanguageList> listLanguages() async {
-        const String path = '/locale/languages';
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/locale/languages';
-        final cacheKey = '';
-        final cacheResponseIdKey = 'code';
-        final cacheResponseContainerKey = 'languages';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.LanguageList.fromMap(res.data);
-
-    }
-}
\ No newline at end of file
+  Locale(super.client);
+
+  /// Get User Locale
+  ///
+  /// Get the current user location based on IP. Returns an object with user
+  /// country code, country name, continent name, continent code, ip address and
+  /// suggested currency. You can use the locale header to get the data in a
+  /// supported language.
+  ///
+  /// ([IP Geolocation by DB-IP](https://db-ip.com))
+  ///
+  Future<models.Locale> get() async {
+    const String path = '/locale';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Locale.fromMap(res.data);
+  }
+
+  /// List Continents
+  ///
+  /// List of all continents. You can use the locale header to get the data in a
+  /// supported language.
+  ///
+  Future<models.ContinentList> listContinents() async {
+    const String path = '/locale/continents';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.ContinentList.fromMap(res.data);
+  }
+
+  /// List Countries
+  ///
+  /// List of all countries. You can use the locale header to get the data in a
+  /// supported language.
+  ///
+  Future<models.CountryList> listCountries() async {
+    const String path = '/locale/countries';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.CountryList.fromMap(res.data);
+  }
+
+  /// List EU Countries
+  ///
+  /// List of all countries that are currently members of the EU. You can use the
+  /// locale header to get the data in a supported language.
+  ///
+  Future<models.CountryList> listCountriesEU() async {
+    const String path = '/locale/countries/eu';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.CountryList.fromMap(res.data);
+  }
+
+  /// List Countries Phone Codes
+  ///
+  /// List of all countries phone codes. You can use the locale header to get the
+  /// data in a supported language.
+  ///
+  Future<models.PhoneList> listCountriesPhones() async {
+    const String path = '/locale/countries/phones';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.PhoneList.fromMap(res.data);
+  }
+
+  /// List Currencies
+  ///
+  /// List of all currencies, including currency symbol, name, plural, and
+  /// decimal digits for all major and minor currencies. You can use the locale
+  /// header to get the data in a supported language.
+  ///
+  Future<models.CurrencyList> listCurrencies() async {
+    const String path = '/locale/currencies';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.CurrencyList.fromMap(res.data);
+  }
+
+  /// List Languages
+  ///
+  /// List of all languages classified by ISO 639-1 including 2-letter code, name
+  /// in English, and name in the respective language.
+  ///
+  Future<models.LanguageList> listLanguages() async {
+    const String path = '/locale/languages';
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.LanguageList.fromMap(res.data);
+  }
+}
diff --git a/lib/services/storage.dart b/lib/services/storage.dart
index d91754dd..2cd86836 100644
--- a/lib/services/storage.dart
+++ b/lib/services/storage.dart
@@ -1,271 +1,274 @@
 part of appwrite;
 
-    /// The Storage service allows you to manage your project files.
+/// The Storage service allows you to manage your project files.
 class Storage extends Service {
-    Storage(super.client);
-
-    /// List Files
-    ///
-    /// Get a list of all the user files. You can use the query params to filter
-    /// your results.
-    ///
-    Future<models.FileList> listFiles({required String bucketId, List<String>? queries, String? search}) async {
-        final String path = '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId);
-
-        final Map<String, dynamic> params = {
-            'queries': queries,
-            'search': search,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = 'files';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.FileList.fromMap(res.data);
-
-    }
-
-    /// Create File
-    ///
-    /// Create a new file. Before using this route, you should create a new bucket
-    /// resource using either a [server
-    /// integration](/docs/server/storage#storageCreateBucket) API or directly from
-    /// your Appwrite console.
-    /// 
-    /// Larger files should be uploaded using multiple requests with the
-    /// [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range)
-    /// header to send a partial request with a maximum supported chunk of `5MB`.
-    /// The `content-range` header values should always be in bytes.
-    /// 
-    /// When the first request is sent, the server will return the **File** object,
-    /// and the subsequent part request must include the file's **id** in
-    /// `x-appwrite-id` header to allow the server to know that the partial upload
-    /// is for the existing file and not for a new one.
-    /// 
-    /// If you're creating a new file using one of the Appwrite SDKs, all the
-    /// chunking logic will be managed by the SDK internally.
-    /// 
-    ///
-    Future<models.File> createFile({required String bucketId, required String fileId, required InputFile file, List<String>? permissions, Function(UploadProgress)? onProgress}) async {
-        final String path = '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId);
-
-        final Map<String, dynamic> params = {
-
-
-            'fileId': fileId,
-            'file': file,
-            'permissions': permissions,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'multipart/form-data',
-        };
-
-        String idParamName = '';
-        idParamName = 'fileId';
-        final paramName = 'file';
-        final res = await client.chunkedUpload(
-            path: path,
-            params: params,
-            paramName: paramName,
-            idParamName: idParamName,
-            headers: headers,
-            onProgress: onProgress,
-          );
-
-        return models.File.fromMap(res.data);
-
-    }
-
-    /// Get File
-    ///
-    /// Get a file by its unique ID. This endpoint response returns a JSON object
-    /// with the file metadata.
-    ///
-    Future<models.File> getFile({required String bucketId, required String fileId}) async {
-        final String path = '/storage/buckets/{bucketId}/files/{fileId}'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.File.fromMap(res.data);
-
-    }
-
-    /// Update File
-    ///
-    /// Update a file by its unique ID. Only users with write permissions have
-    /// access to update this resource.
-    ///
-    Future<models.File> updateFile({required String bucketId, required String fileId, List<String>? permissions}) async {
-        final String path = '/storage/buckets/{bucketId}/files/{fileId}'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId);
-
-        final Map<String, dynamic> params = {
-            'permissions': permissions,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.put,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.File.fromMap(res.data);
-
-    }
-
-    /// Delete File
-    ///
-    /// Delete a file by its unique ID. Only users with write permissions have
-    /// access to delete this resource.
-    ///
-    Future deleteFile({required String bucketId, required String fileId}) async {
-        final String path = '/storage/buckets/{bucketId}/files/{fileId}'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.delete,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-
-    /// Get File for Download
-    ///
-    /// Get a file content by its unique ID. The endpoint response return with a
-    /// 'Content-Disposition: attachment' header that tells the browser to start
-    /// downloading the file to user downloads directory.
-    ///
-    Future<Uint8List> getFileDownload({required String bucketId, required String fileId}) async {
-        final String path = '/storage/buckets/{bucketId}/files/{fileId}/download'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId);
-
-        final Map<String, dynamic> params = {
-            
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get File Preview
-    ///
-    /// Get a file preview image. Currently, this method supports preview for image
-    /// files (jpg, png, and gif), other supported formats, like pdf, docs, slides,
-    /// and spreadsheets, will return the file icon image. You can also pass query
-    /// string arguments for cutting and resizing your preview image. Preview is
-    /// supported only for image files smaller than 10MB.
-    ///
-    Future<Uint8List> getFilePreview({required String bucketId, required String fileId, int? width, int? height, String? gravity, int? quality, int? borderWidth, String? borderColor, int? borderRadius, double? opacity, int? rotation, String? background, String? output}) async {
-        final String path = '/storage/buckets/{bucketId}/files/{fileId}/preview'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId);
-
-        final Map<String, dynamic> params = {
-            
-            'width': width,
-            'height': height,
-            'gravity': gravity,
-            'quality': quality,
-            'borderWidth': borderWidth,
-            'borderColor': borderColor,
-            'borderRadius': borderRadius,
-            'opacity': opacity,
-            'rotation': rotation,
-            'background': background,
-            'output': output,
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-
-    /// Get File for View
-    ///
-    /// Get a file content by its unique ID. This endpoint is similar to the
-    /// download method but returns with no  'Content-Disposition: attachment'
-    /// header.
-    ///
-    Future<Uint8List> getFileView({required String bucketId, required String fileId}) async {
-        final String path = '/storage/buckets/{bucketId}/files/{fileId}/view'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId);
-
-        final Map<String, dynamic> params = {
-            
-            
-            'project': client.config['project'],
-        };
-
-        final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes);
-        return res.data;
-    }
-}
\ No newline at end of file
+  Storage(super.client);
+
+  /// List Files
+  ///
+  /// Get a list of all the user files. You can use the query params to filter
+  /// your results.
+  ///
+  Future<models.FileList> listFiles(
+      {required String bucketId, List<String>? queries, String? search}) async {
+    final String path =
+        '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId);
+
+    final Map<String, dynamic> params = {
+      'queries': queries,
+      'search': search,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.FileList.fromMap(res.data);
+  }
+
+  /// Create File
+  ///
+  /// Create a new file. Before using this route, you should create a new bucket
+  /// resource using either a [server
+  /// integration](/docs/server/storage#storageCreateBucket) API or directly from
+  /// your Appwrite console.
+  ///
+  /// Larger files should be uploaded using multiple requests with the
+  /// [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range)
+  /// header to send a partial request with a maximum supported chunk of `5MB`.
+  /// The `content-range` header values should always be in bytes.
+  ///
+  /// When the first request is sent, the server will return the **File** object,
+  /// and the subsequent part request must include the file's **id** in
+  /// `x-appwrite-id` header to allow the server to know that the partial upload
+  /// is for the existing file and not for a new one.
+  ///
+  /// If you're creating a new file using one of the Appwrite SDKs, all the
+  /// chunking logic will be managed by the SDK internally.
+  ///
+  ///
+  Future<models.File> createFile(
+      {required String bucketId,
+      required String fileId,
+      required InputFile file,
+      List<String>? permissions,
+      Function(UploadProgress)? onProgress}) async {
+    final String path =
+        '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId);
+
+    final Map<String, dynamic> params = {
+      'fileId': fileId,
+      'file': file,
+      'permissions': permissions,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'multipart/form-data',
+    };
+
+    String idParamName = '';
+    idParamName = 'fileId';
+    final paramName = 'file';
+    final res = await client.chunkedUpload(
+      path: path,
+      params: params,
+      paramName: paramName,
+      idParamName: idParamName,
+      headers: headers,
+      onProgress: onProgress,
+    );
+
+    return models.File.fromMap(res.data);
+  }
+
+  /// Get File
+  ///
+  /// Get a file by its unique ID. This endpoint response returns a JSON object
+  /// with the file metadata.
+  ///
+  Future<models.File> getFile(
+      {required String bucketId, required String fileId}) async {
+    final String path = '/storage/buckets/{bucketId}/files/{fileId}'
+        .replaceAll('{bucketId}', bucketId)
+        .replaceAll('{fileId}', fileId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.File.fromMap(res.data);
+  }
+
+  /// Update File
+  ///
+  /// Update a file by its unique ID. Only users with write permissions have
+  /// access to update this resource.
+  ///
+  Future<models.File> updateFile(
+      {required String bucketId,
+      required String fileId,
+      List<String>? permissions}) async {
+    final String path = '/storage/buckets/{bucketId}/files/{fileId}'
+        .replaceAll('{bucketId}', bucketId)
+        .replaceAll('{fileId}', fileId);
+
+    final Map<String, dynamic> params = {
+      'permissions': permissions,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.put,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.File.fromMap(res.data);
+  }
+
+  /// Delete File
+  ///
+  /// Delete a file by its unique ID. Only users with write permissions have
+  /// access to delete this resource.
+  ///
+  Future deleteFile({required String bucketId, required String fileId}) async {
+    final String path = '/storage/buckets/{bucketId}/files/{fileId}'
+        .replaceAll('{bucketId}', bucketId)
+        .replaceAll('{fileId}', fileId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.delete,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+
+  /// Get File for Download
+  ///
+  /// Get a file content by its unique ID. The endpoint response return with a
+  /// 'Content-Disposition: attachment' header that tells the browser to start
+  /// downloading the file to user downloads directory.
+  ///
+  Future<Uint8List> getFileDownload(
+      {required String bucketId, required String fileId}) async {
+    final String path = '/storage/buckets/{bucketId}/files/{fileId}/download'
+        .replaceAll('{bucketId}', bucketId)
+        .replaceAll('{fileId}', fileId);
+
+    final Map<String, dynamic> params = {
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get File Preview
+  ///
+  /// Get a file preview image. Currently, this method supports preview for image
+  /// files (jpg, png, and gif), other supported formats, like pdf, docs, slides,
+  /// and spreadsheets, will return the file icon image. You can also pass query
+  /// string arguments for cutting and resizing your preview image. Preview is
+  /// supported only for image files smaller than 10MB.
+  ///
+  Future<Uint8List> getFilePreview(
+      {required String bucketId,
+      required String fileId,
+      int? width,
+      int? height,
+      String? gravity,
+      int? quality,
+      int? borderWidth,
+      String? borderColor,
+      int? borderRadius,
+      double? opacity,
+      int? rotation,
+      String? background,
+      String? output}) async {
+    final String path = '/storage/buckets/{bucketId}/files/{fileId}/preview'
+        .replaceAll('{bucketId}', bucketId)
+        .replaceAll('{fileId}', fileId);
+
+    final Map<String, dynamic> params = {
+      'width': width,
+      'height': height,
+      'gravity': gravity,
+      'quality': quality,
+      'borderWidth': borderWidth,
+      'borderColor': borderColor,
+      'borderRadius': borderRadius,
+      'opacity': opacity,
+      'rotation': rotation,
+      'background': background,
+      'output': output,
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+
+  /// Get File for View
+  ///
+  /// Get a file content by its unique ID. This endpoint is similar to the
+  /// download method but returns with no  'Content-Disposition: attachment'
+  /// header.
+  ///
+  Future<Uint8List> getFileView(
+      {required String bucketId, required String fileId}) async {
+    final String path = '/storage/buckets/{bucketId}/files/{fileId}/view'
+        .replaceAll('{bucketId}', bucketId)
+        .replaceAll('{fileId}', fileId);
+
+    final Map<String, dynamic> params = {
+      'project': client.config['project'],
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      responseType: ResponseType.bytes,
+    ));
+    return res.data;
+  }
+}
diff --git a/lib/services/teams.dart b/lib/services/teams.dart
index 9532380a..f2762149 100644
--- a/lib/services/teams.dart
+++ b/lib/services/teams.dart
@@ -1,427 +1,342 @@
 part of appwrite;
 
-    /// The Teams service allows you to group users of your project and to enable
-    /// them to share read and write access to your project resources
+/// The Teams service allows you to group users of your project and to enable
+/// them to share read and write access to your project resources
 class Teams extends Service {
-    Teams(super.client);
-
-    /// List Teams
-    ///
-    /// Get a list of all the teams in which the current user is a member. You can
-    /// use the parameters to filter your results.
-    ///
-    Future<models.TeamList> list({List<String>? queries, String? search}) async {
-        const String path = '/teams';
-
-        final Map<String, dynamic> params = {
-            'queries': queries,
-            'search': search,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/teams';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = 'teams';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.TeamList.fromMap(res.data);
-
-    }
-
-    /// Create Team
-    ///
-    /// Create a new team. The user who creates the team will automatically be
-    /// assigned as the owner of the team. Only the users with the owner role can
-    /// invite new members, add new owners and delete or update the team.
-    ///
-    Future<models.Team> create({required String teamId, required String name, List<String>? roles}) async {
-        const String path = '/teams';
-
-        final Map<String, dynamic> params = {
-            'teamId': teamId,
-            'name': name,
-            'roles': roles,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Team.fromMap(res.data);
-
-    }
-
-    /// Get Team
-    ///
-    /// Get a team by its ID. All team members have read access for this resource.
-    ///
-    Future<models.Team> get({required String teamId}) async {
-        final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/teams'.replaceAll('{teamId}', teamId);
-        final cacheKey = teamId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Team.fromMap(res.data);
-
-    }
-
-    /// Update Team
-    ///
-    /// Update a team using its ID. Only members with the owner role can update the
-    /// team.
-    ///
-    Future<models.Team> update({required String teamId, required String name}) async {
-        final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId);
-
-        final Map<String, dynamic> params = {
-            'name': name,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/teams'.replaceAll('{teamId}', teamId);
-        final cacheKey = teamId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.put,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Team.fromMap(res.data);
-
-    }
-
-    /// Delete Team
-    ///
-    /// Delete a team using its ID. Only team members with the owner role can
-    /// delete the team.
-    ///
-    Future delete({required String teamId}) async {
-        final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.delete,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-
-    /// List Team Memberships
-    ///
-    /// Use this endpoint to list a team's members using the team's ID. All team
-    /// members have read access to this endpoint.
-    ///
-    Future<models.MembershipList> listMemberships({required String teamId, List<String>? queries, String? search}) async {
-        final String path = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId);
-
-        final Map<String, dynamic> params = {
-            'queries': queries,
-            'search': search,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId);
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = 'memberships';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.MembershipList.fromMap(res.data);
-
-    }
-
-    /// Create Team Membership
-    ///
-    /// Invite a new member to join your team. If initiated from the client SDK, an
-    /// email with a link to join the team will be sent to the member's email
-    /// address and an account will be created for them should they not be signed
-    /// up already. If initiated from server-side SDKs, the new member will
-    /// automatically be added to the team.
-    /// 
-    /// Use the 'url' parameter to redirect the user from the invitation email back
-    /// to your app. When the user is redirected, use the [Update Team Membership
-    /// Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow
-    /// the user to accept the invitation to the team. 
-    /// 
-    /// Please note that to avoid a [Redirect
-    /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
-    /// the only valid redirect URL's are the once from domains you have set when
-    /// adding your platforms in the console interface.
-    ///
-    Future<models.Membership> createMembership({required String teamId, required String email, required List<String> roles, required String url, String? name}) async {
-        final String path = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId);
-
-        final Map<String, dynamic> params = {
-            'email': email,
-            'roles': roles,
-            'url': url,
-            'name': name,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.post,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Membership.fromMap(res.data);
-
-    }
-
-    /// Get Team Membership
-    ///
-    /// Get a team member by the membership unique id. All team members have read
-    /// access for this resource.
-    ///
-    Future<models.Membership> getMembership({required String teamId, required String membershipId}) async {
-        final String path = '/teams/{teamId}/memberships/{membershipId}'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
-        final cacheKey = membershipId;
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.get,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Membership.fromMap(res.data);
-
-    }
-
-    /// Update Membership Roles
-    ///
-    /// Modify the roles of a team member. Only team members with the owner role
-    /// have access to this endpoint. Learn more about [roles and
-    /// permissions](/docs/permissions).
-    ///
-    Future<models.Membership> updateMembershipRoles({required String teamId, required String membershipId, required List<String> roles}) async {
-        final String path = '/teams/{teamId}/memberships/{membershipId}'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
-
-        final Map<String, dynamic> params = {
-            'roles': roles,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Membership.fromMap(res.data);
-
-    }
-
-    /// Delete Team Membership
-    ///
-    /// This endpoint allows a user to leave a team or for a team owner to delete
-    /// the membership of any other team member. You can also use this endpoint to
-    /// delete a user membership even if it is not accepted.
-    ///
-    Future deleteMembership({required String teamId, required String membershipId}) async {
-        final String path = '/teams/{teamId}/memberships/{membershipId}'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
-
-        final Map<String, dynamic> params = {
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.delete,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return  res.data;
-
-    }
-
-    /// Update Team Membership Status
-    ///
-    /// Use this endpoint to allow a user to accept an invitation to join a team
-    /// after being redirected back to your app from the invitation email received
-    /// by the user.
-    /// 
-    /// If the request is successful, a session for the user is automatically
-    /// created.
-    /// 
-    ///
-    Future<models.Membership> updateMembershipStatus({required String teamId, required String membershipId, required String userId, required String secret}) async {
-        final String path = '/teams/{teamId}/memberships/{membershipId}/status'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId);
-
-        final Map<String, dynamic> params = {
-            'userId': userId,
-            'secret': secret,
-        };
-
-        final Map<String, String> headers = {
-            'content-type': 'application/json',
-        };
-
-        final cacheModel = '';
-        final cacheKey = '';
-        final cacheResponseIdKey = '\$id';
-        final cacheResponseContainerKey = '';
-
-        final res = await client.call(
-            HttpMethod.patch,
-            path: path,
-            params: params,
-            headers: headers,
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        return models.Membership.fromMap(res.data);
-
-    }
-}
\ No newline at end of file
+  Teams(super.client);
+
+  /// List Teams
+  ///
+  /// Get a list of all the teams in which the current user is a member. You can
+  /// use the parameters to filter your results.
+  ///
+  Future<models.TeamList> list({List<String>? queries, String? search}) async {
+    const String path = '/teams';
+
+    final Map<String, dynamic> params = {
+      'queries': queries,
+      'search': search,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.TeamList.fromMap(res.data);
+  }
+
+  /// Create Team
+  ///
+  /// Create a new team. The user who creates the team will automatically be
+  /// assigned as the owner of the team. Only the users with the owner role can
+  /// invite new members, add new owners and delete or update the team.
+  ///
+  Future<models.Team> create(
+      {required String teamId,
+      required String name,
+      List<String>? roles}) async {
+    const String path = '/teams';
+
+    final Map<String, dynamic> params = {
+      'teamId': teamId,
+      'name': name,
+      'roles': roles,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Team.fromMap(res.data);
+  }
+
+  /// Get Team
+  ///
+  /// Get a team by its ID. All team members have read access for this resource.
+  ///
+  Future<models.Team> get({required String teamId}) async {
+    final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Team.fromMap(res.data);
+  }
+
+  /// Update Team
+  ///
+  /// Update a team using its ID. Only members with the owner role can update the
+  /// team.
+  ///
+  Future<models.Team> update(
+      {required String teamId, required String name}) async {
+    final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId);
+
+    final Map<String, dynamic> params = {
+      'name': name,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.put,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Team.fromMap(res.data);
+  }
+
+  /// Delete Team
+  ///
+  /// Delete a team using its ID. Only team members with the owner role can
+  /// delete the team.
+  ///
+  Future delete({required String teamId}) async {
+    final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.delete,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+
+  /// List Team Memberships
+  ///
+  /// Use this endpoint to list a team's members using the team's ID. All team
+  /// members have read access to this endpoint.
+  ///
+  Future<models.MembershipList> listMemberships(
+      {required String teamId, List<String>? queries, String? search}) async {
+    final String path =
+        '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId);
+
+    final Map<String, dynamic> params = {
+      'queries': queries,
+      'search': search,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.MembershipList.fromMap(res.data);
+  }
+
+  /// Create Team Membership
+  ///
+  /// Invite a new member to join your team. If initiated from the client SDK, an
+  /// email with a link to join the team will be sent to the member's email
+  /// address and an account will be created for them should they not be signed
+  /// up already. If initiated from server-side SDKs, the new member will
+  /// automatically be added to the team.
+  ///
+  /// Use the 'url' parameter to redirect the user from the invitation email back
+  /// to your app. When the user is redirected, use the [Update Team Membership
+  /// Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow
+  /// the user to accept the invitation to the team.
+  ///
+  /// Please note that to avoid a [Redirect
+  /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md)
+  /// the only valid redirect URL's are the once from domains you have set when
+  /// adding your platforms in the console interface.
+  ///
+  Future<models.Membership> createMembership(
+      {required String teamId,
+      required String email,
+      required List<String> roles,
+      required String url,
+      String? name}) async {
+    final String path =
+        '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId);
+
+    final Map<String, dynamic> params = {
+      'email': email,
+      'roles': roles,
+      'url': url,
+      'name': name,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.post,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Membership.fromMap(res.data);
+  }
+
+  /// Get Team Membership
+  ///
+  /// Get a team member by the membership unique id. All team members have read
+  /// access for this resource.
+  ///
+  Future<models.Membership> getMembership(
+      {required String teamId, required String membershipId}) async {
+    final String path = '/teams/{teamId}/memberships/{membershipId}'
+        .replaceAll('{teamId}', teamId)
+        .replaceAll('{membershipId}', membershipId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.get,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Membership.fromMap(res.data);
+  }
+
+  /// Update Membership Roles
+  ///
+  /// Modify the roles of a team member. Only team members with the owner role
+  /// have access to this endpoint. Learn more about [roles and
+  /// permissions](/docs/permissions).
+  ///
+  Future<models.Membership> updateMembershipRoles(
+      {required String teamId,
+      required String membershipId,
+      required List<String> roles}) async {
+    final String path = '/teams/{teamId}/memberships/{membershipId}'
+        .replaceAll('{teamId}', teamId)
+        .replaceAll('{membershipId}', membershipId);
+
+    final Map<String, dynamic> params = {
+      'roles': roles,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Membership.fromMap(res.data);
+  }
+
+  /// Delete Team Membership
+  ///
+  /// This endpoint allows a user to leave a team or for a team owner to delete
+  /// the membership of any other team member. You can also use this endpoint to
+  /// delete a user membership even if it is not accepted.
+  ///
+  Future deleteMembership(
+      {required String teamId, required String membershipId}) async {
+    final String path = '/teams/{teamId}/memberships/{membershipId}'
+        .replaceAll('{teamId}', teamId)
+        .replaceAll('{membershipId}', membershipId);
+
+    final Map<String, dynamic> params = {};
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.delete,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return res.data;
+  }
+
+  /// Update Team Membership Status
+  ///
+  /// Use this endpoint to allow a user to accept an invitation to join a team
+  /// after being redirected back to your app from the invitation email received
+  /// by the user.
+  ///
+  /// If the request is successful, a session for the user is automatically
+  /// created.
+  ///
+  ///
+  Future<models.Membership> updateMembershipStatus(
+      {required String teamId,
+      required String membershipId,
+      required String userId,
+      required String secret}) async {
+    final String path = '/teams/{teamId}/memberships/{membershipId}/status'
+        .replaceAll('{teamId}', teamId)
+        .replaceAll('{membershipId}', membershipId);
+
+    final Map<String, dynamic> params = {
+      'userId': userId,
+      'secret': secret,
+    };
+
+    final Map<String, String> headers = {
+      'content-type': 'application/json',
+    };
+
+    final res = await client.call(CallParams(
+      HttpMethod.patch,
+      path,
+      params: params,
+      headers: headers,
+    ));
+
+    return models.Membership.fromMap(res.data);
+  }
+}
diff --git a/lib/src/call_handlers/call_handler.dart b/lib/src/call_handlers/call_handler.dart
new file mode 100644
index 00000000..1e6fb404
--- /dev/null
+++ b/lib/src/call_handlers/call_handler.dart
@@ -0,0 +1,12 @@
+import '../call_params.dart';
+import '../response.dart';
+
+abstract class CallHandler {
+  late CallHandler next;
+
+  void setNext(CallHandler next) {
+    this.next = next;
+  }
+
+  Future<Response> handleCall(CallParams params);
+}
diff --git a/lib/src/call_handlers/cookie_auth_call_handler.dart b/lib/src/call_handlers/cookie_auth_call_handler.dart
new file mode 100644
index 00000000..e2455fef
--- /dev/null
+++ b/lib/src/call_handlers/cookie_auth_call_handler.dart
@@ -0,0 +1,42 @@
+import 'dart:io';
+
+import 'package:cookie_jar/cookie_jar.dart';
+
+import '../call_params.dart';
+import '../response.dart';
+import 'call_handler.dart';
+import 'http_call_handler.dart';
+
+class CookieAuthCallHandler extends CallHandler {
+  final CookieJar cookieJar;
+
+  CookieAuthCallHandler(this.cookieJar);
+
+  @override
+  Future<Response> handleCall(CallParams params) async {
+    final endpoint = getEndpoint(params);
+    final uri = Uri.parse(endpoint + params.path);
+    try {
+      final cookies = await cookieJar.loadForRequest(uri);
+      var cookie =
+          cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');
+      if (cookie.isNotEmpty) {
+        params.headers.addAll({HttpHeaders.cookieHeader: cookie});
+      }
+    } catch (_) {}
+
+    final response = await next.handleCall(params);
+
+    final cookie = response.headers[HttpHeaders.setCookieHeader];
+    if (cookie == null) return response;
+
+    var exp = RegExp(r',(?=[^ ])');
+    var cookies = cookie.split(exp);
+    await cookieJar.saveFromResponse(
+      Uri(scheme: uri.scheme, host: uri.host),
+      cookies.map((str) => Cookie.fromSetCookieValue(str)).toList(),
+    );
+
+    return response;
+  }
+}
diff --git a/lib/src/call_handlers/fallback_auth_call_handler.dart b/lib/src/call_handlers/fallback_auth_call_handler.dart
new file mode 100644
index 00000000..837e999c
--- /dev/null
+++ b/lib/src/call_handlers/fallback_auth_call_handler.dart
@@ -0,0 +1,31 @@
+import 'package:flutter/foundation.dart';
+import 'package:universal_html/html.dart' as html;
+
+import '../call_params.dart';
+import '../response.dart';
+import 'call_handler.dart';
+
+const _cookieFallbackKey = 'cookieFallback';
+
+class FallbackAuthCallHandler extends CallHandler {
+  FallbackAuthCallHandler();
+
+  @override
+  Future<Response> handleCall(CallParams params) async {
+    if (html.window.localStorage.keys.contains(_cookieFallbackKey)) {
+      final cookieFallback = html.window.localStorage[_cookieFallbackKey];
+      params.headers.addAll({'x-fallback-cookies': cookieFallback!});
+    }
+
+    final response = await next.handleCall(params);
+
+    final cookieFallback = response.headers['x-fallback-cookies'];
+    if (cookieFallback != null) {
+      debugPrint(
+          'Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
+      html.window.localStorage[_cookieFallbackKey] = cookieFallback;
+    }
+
+    return response;
+  }
+}
diff --git a/lib/src/call_handlers/http_call_handler.dart b/lib/src/call_handlers/http_call_handler.dart
new file mode 100644
index 00000000..b72648a6
--- /dev/null
+++ b/lib/src/call_handlers/http_call_handler.dart
@@ -0,0 +1,51 @@
+import 'package:http/http.dart' as http;
+
+import '../call_params.dart';
+import '../client_mixin.dart';
+import '../exception.dart';
+import '../response.dart';
+import 'call_handler.dart';
+
+const endpointKey = 'endpoint';
+
+String getEndpoint(CallParams params) {
+  final endpoint = params.context[endpointKey];
+  if (endpoint == null) return 'https://HOSTNAME/v1';
+  return endpoint as String;
+}
+
+CallParams withEndpoint(CallParams params, String endpoint) {
+  params.context[endpointKey] = endpoint;
+  return params;
+}
+
+class HttpCallHandler extends CallHandler with ClientMixin {
+  final http.Client client;
+
+  HttpCallHandler(this.client);
+
+  @override
+  Future<Response> handleCall(CallParams params) async {
+    final endpoint = getEndpoint(params);
+
+    late http.Response res;
+    http.BaseRequest request = prepareRequest(
+      params.method,
+      uri: Uri.parse(endpoint + params.path),
+      headers: params.headers,
+      params: params.params,
+    );
+
+    try {
+      final streamedResponse = await client.send(request);
+      res = await toResponse(streamedResponse);
+
+      return prepareResponse(res, responseType: params.responseType);
+    } catch (e) {
+      if (e is AppwriteException) {
+        rethrow;
+      }
+      throw AppwriteException(e.toString());
+    }
+  }
+}
diff --git a/lib/src/call_handlers/offline_call_handler.dart b/lib/src/call_handlers/offline_call_handler.dart
new file mode 100644
index 00000000..b20482d1
--- /dev/null
+++ b/lib/src/call_handlers/offline_call_handler.dart
@@ -0,0 +1,209 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:http/http.dart' as http;
+
+import '../call_params.dart';
+import '../client_mixin.dart';
+import '../client_offline_mixin.dart';
+import '../enums.dart';
+import '../exception.dart';
+import '../offline/caller.dart';
+import '../offline/route.dart';
+import '../offline/route_mapping.dart';
+import '../offline/router.dart';
+import '../response.dart';
+import 'call_handler.dart';
+import 'http_call_handler.dart';
+
+const offlinePersistencyKey = 'offlinePersistency';
+
+bool getOfflinePersistency(CallParams params) {
+  final offlinePersistency = params.context[offlinePersistencyKey];
+  if (offlinePersistency == null) return false;
+  return offlinePersistency as bool;
+}
+
+CallParams withOfflinePersistency(CallParams params, bool status) {
+  params.context[offlinePersistencyKey] = status;
+  return params;
+}
+
+class CacheParams {
+  final String model;
+  final String key;
+  final String responseIdKey;
+  final String responseContainerKey;
+  final Map<String, Object?>? previous;
+
+  CacheParams({
+    this.model = '',
+    this.key = '',
+    this.responseIdKey = '',
+    this.responseContainerKey = '',
+    this.previous,
+  });
+}
+
+const cacheParamsKey = 'cacheParams';
+
+CacheParams getCacheParams(CallParams params) {
+  final cacheParams = params.context[cacheParamsKey];
+  if (cacheParams == null) return CacheParams();
+  return cacheParams as CacheParams;
+}
+
+CallParams withCacheParams(CallParams params, CacheParams cacheParams) {
+  params.context[cacheParamsKey] = cacheParams;
+  return params;
+}
+
+class OfflineCallHandler extends CallHandler
+    with ClientMixin, ClientOfflineMixin {
+  final Caller _call;
+  final Router _router = Router();
+
+  OfflineCallHandler(this._call) {
+    routes.forEach((route) {
+      final method = (route['method'] as String).toUpperCase();
+      final path = route['path'] as String;
+      final offline = route['offline'] as Map<String, String>;
+      _router.addRoute(
+        Route(method, path)
+            .label('offline.model', offline['model']!)
+            .label('offline.key', offline['key']!)
+            .label('offline.response-key', offline['response-key']!)
+            .label('offline.container-key', offline['container-key']!),
+      );
+    });
+  }
+
+  @override
+  Future<Response> handleCall(CallParams params) async {
+    final endpoint = getEndpoint(params);
+    final offlinePersistency = getOfflinePersistency(params);
+
+    if (offlinePersistency) {
+      params.headers['X-SDK-Offline'] = 'true';
+    }
+
+    while (true) {
+      try {
+        // if offline, do offline stuff
+        print('checking offline status...');
+
+        final routeMatch = _router.match(
+          params.method.name(),
+          params.path,
+        );
+
+        final modelPattern = routeMatch?.getLabel('offline.model') as String;
+
+        final pathValues = routeMatch?.getPathValues(params.path);
+
+        String replacePlaceholder(
+            String input, Map<String, String>? pathValues) {
+          if (!input.startsWith('{') || !input.endsWith('}')) {
+            return input;
+          }
+          return pathValues![input.substring(1, input.length - 1)]!;
+        }
+
+        final model = modelPattern.split('/').map((part) {
+          return replacePlaceholder(part, pathValues);
+        }).join('/');
+
+        final keyPattern = routeMatch?.getLabel('offline.key') ?? '';
+        final key = replacePlaceholder(
+          keyPattern,
+          pathValues,
+        );
+
+        final containerKeyPattern =
+            routeMatch?.getLabel('offline.container-key') ?? '';
+        final containerKey = replacePlaceholder(
+          containerKeyPattern,
+          pathValues,
+        );
+
+        final cacheParams = CacheParams(
+          model: model,
+          key: key,
+          responseIdKey: routeMatch?.getLabel('offline.response-key') as String,
+          responseContainerKey: containerKey,
+        );
+
+        final uri = Uri.parse(endpoint + params.path);
+
+        http.BaseRequest request = prepareRequest(
+          params.method,
+          uri: Uri.parse(endpoint + params.path),
+          headers: params.headers,
+          params: params.params,
+        );
+
+        if (offlinePersistency && !isOnline.value) {
+          await checkOnlineStatus();
+        }
+
+        if (cacheParams.model.isNotEmpty &&
+            offlinePersistency &&
+            !isOnline.value &&
+            params.responseType != ResponseType.bytes) {
+          return handleOfflineRequest(
+            uri: uri,
+            method: params.method,
+            call: this._call,
+            path: params.path,
+            headers: params.headers,
+            params: params.params,
+            responseType: params.responseType,
+            cacheModel: cacheParams.model,
+            cacheKey: cacheParams.key,
+            cacheResponseIdKey: cacheParams.responseIdKey,
+            cacheResponseContainerKey: cacheParams.responseContainerKey,
+          );
+        }
+
+        final response = await next.handleCall(params);
+
+        if (offlinePersistency) {
+          // cache stuff
+          print('cached stuff...');
+
+          final relationsHeader = response.headers['x-appwrite-relations'];
+          if (relationsHeader != null) {
+            final relations = (jsonDecode(relationsHeader) as List)
+                .cast<Map<String, dynamic>>();
+            await cacheCollections(relations);
+          }
+          cacheResponse(
+            cacheModel: cacheParams.model,
+            cacheKey: cacheParams.key,
+            cacheResponseIdKey: cacheParams.responseIdKey,
+            cacheResponseContainerKey: cacheParams.responseContainerKey,
+            requestMethod: request.method,
+            responseData: response.data,
+          );
+        }
+
+        return response;
+      } on AppwriteException catch (e) {
+        if ((e.message != "Network is unreachable" &&
+                !(e.message?.contains("Failed host lookup") ?? false)) ||
+            !offlinePersistency) {
+          rethrow;
+        }
+        isOnline.value = false;
+      } on SocketException catch (_) {
+        if (!offlinePersistency) {
+          rethrow;
+        }
+        isOnline.value = false;
+      } catch (e, s) {
+        print(s);
+        throw AppwriteException(e.toString());
+      }
+    }
+  }
+}
diff --git a/lib/src/call_params.dart b/lib/src/call_params.dart
new file mode 100644
index 00000000..baa171d6
--- /dev/null
+++ b/lib/src/call_params.dart
@@ -0,0 +1,23 @@
+import 'enums.dart';
+
+class CallParams {
+  final HttpMethod method;
+  final String path;
+  final Map<String, String> headers = {};
+  final Map<String, dynamic> params = {};
+  final ResponseType? responseType;
+  final Map<String, Object?> context = {};
+
+  CallParams(
+    this.method,
+    this.path, {
+    this.responseType,
+    Map<String, String> headers = const {},
+    Map<String, dynamic> params = const {},
+    Map<String, Object?> context = const {},
+  }) {
+    this.headers.addAll(headers);
+    this.params.addAll(params);
+    this.context.addAll(context);
+  }
+}
diff --git a/lib/src/client.dart b/lib/src/client.dart
index 88cb62d7..cf9aa2b1 100644
--- a/lib/src/client.dart
+++ b/lib/src/client.dart
@@ -1,7 +1,8 @@
+import 'call_handlers/call_handler.dart';
+import 'call_params.dart';
 import 'client_stub.dart'
     if (dart.library.html) 'client_browser.dart'
     if (dart.library.io) 'client_io.dart';
-import 'enums.dart';
 import 'response.dart';
 import 'upload_progress.dart';
 
@@ -39,24 +40,14 @@ abstract class Client {
 
   /// Your project ID
   Client setProject(value);
+
   /// Your secret JSON Web Token
   Client setJWT(value);
   Client setLocale(value);
 
   Client addHeader(String key, String value);
 
-  Future<Response> call(
-    HttpMethod method, {
-    String path = '',
-    Map<String, String> headers = const {},
-    Map<String, dynamic> params = const {},
-    ResponseType? responseType,
-    String cacheModel = '',
-    String cacheKey = '',
-    String cacheResponseIdKey = '',
-    String cacheResponseContainerKey = '',
-    Map<String, Object?>? previous,
-  });
+  Future<Response> call(CallParams params);
 
   Future<Client> setOfflinePersistency({
     bool status = true,
@@ -68,4 +59,6 @@ abstract class Client {
   Client setOfflineCacheSize(int kbytes);
 
   int getOfflineCacheSize();
+
+  Client addHandler(CallHandler handler);
 }
diff --git a/lib/src/client_base.dart b/lib/src/client_base.dart
index cdfe8a82..375f0a96 100644
--- a/lib/src/client_base.dart
+++ b/lib/src/client_base.dart
@@ -1,57 +1,37 @@
+import 'call_handlers/call_handler.dart';
+import 'call_params.dart';
 import 'client.dart';
-import 'enums.dart';
 import 'response.dart';
 
 abstract class ClientBase implements Client {
   /// Your project ID
-  @override
   ClientBase setProject(value);
 
   /// Your secret JSON Web Token
-  @override
   ClientBase setJWT(value);
 
-  @override
   ClientBase setLocale(value);
 
-  @override
   ClientBase setSelfSigned({bool status = true});
 
-  @override
   ClientBase setEndpoint(String endPoint);
 
-  @override
   Client setEndPointRealtime(String endPoint);
 
-  @override
   ClientBase addHeader(String key, String value);
 
-  @override
-  Future<Response> call(
-    HttpMethod method, {
-    String path = '',
-    Map<String, String> headers = const {},
-    Map<String, dynamic> params = const {},
-    ResponseType? responseType,
-    String cacheModel = '',
-    String cacheKey = '',
-    String cacheResponseIdKey = '',
-    String cacheResponseContainerKey = '',
-    Map<String, Object?>? previous,
-  });
+  Future<Response> call(CallParams params);
 
-  @override
   Future<ClientBase> setOfflinePersistency({
     bool status = true,
     void Function(Object)? onWriteQueueError,
   });
 
-  @override
   bool getOfflinePersistency();
 
-  @override
   ClientBase setOfflineCacheSize(int kbytes);
 
-  @override
   int getOfflineCacheSize();
+
+  ClientBase addHandler(CallHandler handler);
 }
diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart
index 79db3dbd..83054ee9 100644
--- a/lib/src/client_browser.dart
+++ b/lib/src/client_browser.dart
@@ -1,15 +1,16 @@
-import 'dart:io';
 import 'dart:math';
 
-import 'package:flutter/foundation.dart';
 import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
 import 'package:http/browser_client.dart';
 import 'package:http/http.dart' as http;
-import 'package:universal_html/html.dart' as html;
 
+import 'call_handlers/call_handler.dart';
+import 'call_handlers/fallback_auth_call_handler.dart';
+import 'call_handlers/http_call_handler.dart';
+import 'call_handlers/offline_call_handler.dart';
+import 'call_params.dart';
 import 'client_base.dart';
 import 'client_mixin.dart';
-import 'client_offline_mixin.dart';
 import 'enums.dart';
 import 'exception.dart';
 import 'input_file.dart';
@@ -22,7 +23,7 @@ ClientBase createClient({
 }) =>
     ClientBrowser(endPoint: endPoint, selfSigned: selfSigned);
 
-class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
+class ClientBrowser extends ClientBase with ClientMixin {
   static const int CHUNK_SIZE = 5 * 1024 * 1024;
   String _endPoint;
   Map<String, String>? _headers;
@@ -30,6 +31,8 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
   late Map<String, String> config;
   late BrowserClient _httpClient;
   String? _endPointRealtime;
+  late CallHandler _handler;
+  late OfflineCallHandler _offlineHandler;
   bool _offlinePersistency = false;
   int _maxCacheSize = 40000; // 40MB
 
@@ -57,6 +60,9 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
 
     assert(_endPoint.startsWith(RegExp("http://|https://")),
         "endPoint $_endPoint must start with 'http'");
+    _handler = HttpCallHandler(_httpClient);
+    _offlineHandler = OfflineCallHandler(call);
+
     init();
   }
 
@@ -116,7 +122,7 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
     _offlinePersistency = status;
 
     if (_offlinePersistency) {
-      await initOffline(
+      await _offlineHandler.initOffline(
         call: call,
         onWriteQueueError: onWriteQueueError,
         getOfflineCacheSize: getOfflineCacheSize,
@@ -146,10 +152,8 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
   }
 
   Future init() async {
-    if (html.window.localStorage.keys.contains('cookieFallback')) {
-      addHeader('x-fallback-cookies',
-          html.window.localStorage['cookieFallback'] ?? '');
-    }
+    addHandler(FallbackAuthCallHandler());
+    addHandler(_offlineHandler);
     _httpClient.withCredentials = true;
   }
 
@@ -176,23 +180,23 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
         file.bytes!,
         filename: file.filename,
       );
-      return call(
+      return call(CallParams(
         HttpMethod.post,
-        path: path,
+        path,
         params: params,
         headers: headers,
-      );
+      ));
     }
 
     var offset = 0;
     if (idParamName.isNotEmpty && params[idParamName] != 'unique()') {
       //make a request to check if a file already exists
       try {
-        res = await call(
+        res = await call(CallParams(
           HttpMethod.get,
-          path: path + '/' + params[idParamName],
+          path + '/' + params[idParamName],
           headers: headers,
-        );
+        ));
         final int chunksUploaded = res.data['chunksUploaded'] as int;
         offset = min(size, chunksUploaded * CHUNK_SIZE);
       } on AppwriteException catch (_) {}
@@ -209,8 +213,12 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
       );
       headers['content-range'] =
           'bytes $offset-${min<int>(((offset + CHUNK_SIZE) - 1), size)}/$size';
-      res = await call(HttpMethod.post,
-          path: path, headers: headers, params: params);
+      res = await call(CallParams(
+        HttpMethod.post,
+        path,
+        headers: headers,
+        params: params,
+      ));
       offset += CHUNK_SIZE;
       if (offset < size) {
         headers['x-appwrite-id'] = res.data['\$id'];
@@ -228,132 +236,16 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
   }
 
   @override
-  Future<Response> call(
-    HttpMethod method, {
-    String path = '',
-    Map<String, String> headers = const {},
-    Map<String, dynamic> params = const {},
-    ResponseType? responseType,
-    String cacheModel = '',
-    String cacheKey = '',
-    String cacheResponseIdKey = '',
-    String cacheResponseContainerKey = '',
-    Map<String, Object?>? previous,
-  }) async {
-    while (true) {
-      final uri = Uri.parse(endPoint + path);
-
-      http.BaseRequest request = prepareRequest(
-        method,
-        uri: uri,
-        headers: {..._headers!, ...headers},
-        params: params,
-      );
-
-      if (getOfflinePersistency() && !isOnline.value) {
-        await checkOnlineStatus();
-      }
-
-      if (cacheModel.isNotEmpty &&
-          getOfflinePersistency() &&
-          !isOnline.value &&
-          responseType != ResponseType.bytes) {
-        return handleOfflineRequest(
-          uri: uri,
-          method: method,
-          call: call,
-          path: path,
-          headers: headers,
-          params: params,
-          responseType: responseType,
-          cacheModel: cacheModel,
-          cacheKey: cacheKey,
-          cacheResponseIdKey: cacheResponseIdKey,
-          cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-      }
-
-      try {
-        final response = await send(
-          method,
-          path: path,
-          headers: headers,
-          params: params,
-          responseType: responseType,
-          cacheModel: cacheModel,
-          cacheKey: cacheKey,
-          cacheResponseIdKey: cacheResponseIdKey,
-          cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        if (getOfflinePersistency()) {
-          cacheResponse(
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            request: request,
-            response: response,
-          );
-        }
-
-        return response;
-      } on AppwriteException catch (e) {
-        if ((e.message != "Network is unreachable" &&
-                !(e.message?.contains("Failed host lookup") ?? false)) ||
-            !getOfflinePersistency()) {
-          rethrow;
-        }
-        isOnline.value = false;
-      } on SocketException catch (_) {
-        if (!getOfflinePersistency()) {
-          rethrow;
-        }
-        isOnline.value = false;
-      } catch (e) {
-        throw AppwriteException(e.toString());
-      }
-    }
-  }
-
-  Future<Response> send(
-    HttpMethod method, {
-    String path = '',
-    Map<String, String> headers = const {},
-    Map<String, dynamic> params = const {},
-    ResponseType? responseType,
-    String cacheModel = '',
-    String cacheKey = '',
-    String cacheResponseIdKey = '',
-    String cacheResponseContainerKey = '',
-    Map<String, Object?>? previous,
-  }) async {
-    await init();
-
-    late http.Response res;
-    http.BaseRequest request = prepareRequest(
-      method,
-      uri: Uri.parse(_endPoint + path),
-      headers: {..._headers!, ...headers},
-      params: params,
+  Future<Response> call(CallParams params) async {
+    params.headers.addAll(this._headers!);
+    final response = await _handler.handleCall(
+      withOfflinePersistency(
+        withEndpoint(params, endPoint),
+        getOfflinePersistency(),
+      ),
     );
-    try {
-      final streamedResponse = await _httpClient.send(request);
-      res = await toResponse(streamedResponse);
-
-      final cookieFallback = res.headers['x-fallback-cookies'];
-      if (cookieFallback != null) {
-        debugPrint(
-            'Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.');
-        addHeader('X-Fallback-Cookies', cookieFallback);
-        html.window.localStorage['cookieFallback'] = cookieFallback;
-      }
-      return prepareResponse(res, responseType: responseType);
-    } catch (e) {
-      if (e is AppwriteException) {
-        rethrow;
-      }
-      throw AppwriteException(e.toString());
-    }
+
+    return response;
   }
 
   @override
@@ -363,4 +255,11 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin {
       callbackUrlScheme: "appwrite-callback-" + config['project']!,
     );
   }
+
+  @override
+  ClientBrowser addHandler(CallHandler handler) {
+    handler.setNext(_handler);
+    _handler = handler;
+    return this;
+  }
 }
diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart
index 3b29c760..e4f089c5 100644
--- a/lib/src/client_io.dart
+++ b/lib/src/client_io.dart
@@ -11,14 +11,16 @@ import 'package:http/io_client.dart';
 import 'package:package_info_plus/package_info_plus.dart';
 import 'package:path_provider/path_provider.dart';
 
+import 'call_handlers/call_handler.dart';
+import 'call_handlers/cookie_auth_call_handler.dart';
+import 'call_handlers/http_call_handler.dart';
+import 'call_handlers/offline_call_handler.dart';
+import 'call_params.dart';
 import 'client_base.dart';
 import 'client_mixin.dart';
-import 'client_offline_mixin.dart';
-import 'cookie_manager.dart';
 import 'enums.dart';
 import 'exception.dart';
 import 'input_file.dart';
-import 'interceptor.dart';
 import 'response.dart';
 import 'upload_progress.dart';
 
@@ -31,7 +33,7 @@ ClientBase createClient({
       selfSigned: selfSigned,
     );
 
-class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
+class ClientIO extends ClientBase with ClientMixin {
   static const int CHUNK_SIZE = 5 * 1024 * 1024;
   String _endPoint;
   Map<String, String>? _headers;
@@ -44,7 +46,8 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
   late http.Client _httpClient;
   late HttpClient _nativeClient;
   late CookieJar _cookieJar;
-  final List<Interceptor> _interceptors = [];
+  late CallHandler _handler;
+  late OfflineCallHandler _offlineHandler;
 
   bool get initProgress => _initProgress;
   bool get initialized => _initialized;
@@ -78,6 +81,10 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
 
     assert(_endPoint.startsWith(RegExp("http://|https://")),
         "endPoint $_endPoint must start with 'http'");
+
+    _handler = HttpCallHandler(_httpClient);
+    _offlineHandler = OfflineCallHandler(call);
+
     init();
   }
 
@@ -148,7 +155,7 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
     _offlinePersistency = status;
 
     if (_offlinePersistency) {
-      await initOffline(
+      await _offlineHandler.initOffline(
         call: call,
         onWriteQueueError: onWriteQueueError,
         getOfflineCacheSize: getOfflineCacheSize,
@@ -182,7 +189,8 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
     _initProgress = true;
     final Directory cookieDir = await _getCookiePath();
     _cookieJar = PersistCookieJar(storage: FileStorage(cookieDir.path));
-    _interceptors.add(CookieManager(_cookieJar));
+    addHandler(CookieAuthCallHandler(_cookieJar));
+    addHandler(_offlineHandler);
 
     var device = '';
     try {
@@ -230,35 +238,6 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
     _initProgress = false;
   }
 
-  Future<http.BaseRequest> _interceptRequest(http.BaseRequest request) async {
-    final body = (request is http.Request) ? request.body : '';
-    for (final i in _interceptors) {
-      request = await i.onRequest(request);
-    }
-
-    if (request is http.Request) {
-      assert(
-        body == request.body,
-        'Interceptors should not transform the body of the request'
-        'Use Request converter instead',
-      );
-    }
-    return request;
-  }
-
-  Future<http.Response> _interceptResponse(http.Response response) async {
-    final body = response.body;
-    for (final i in _interceptors) {
-      response = await i.onResponse(response);
-    }
-
-    assert(
-      body == response.body,
-      'Interceptors should not transform the body of the response',
-    );
-    return response;
-  }
-
   @override
   Future<Response> chunkedUpload({
     required String path,
@@ -300,23 +279,23 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
           filename: file.filename,
         );
       }
-      return call(
+      return call(CallParams(
         HttpMethod.post,
-        path: path,
+        path,
         params: params,
         headers: headers,
-      );
+      ));
     }
 
     var offset = 0;
     if (idParamName.isNotEmpty && params[idParamName] != 'unique()') {
       //make a request to check if a file already exists
       try {
-        res = await call(
+        res = await call(CallParams(
           HttpMethod.get,
-          path: path + '/' + params[idParamName],
+          path + '/' + params[idParamName],
           headers: headers,
-        );
+        ));
         final int chunksUploaded = res.data['chunksUploaded'] as int;
         offset = min(size, chunksUploaded * CHUNK_SIZE);
       } on AppwriteException catch (_) {}
@@ -344,12 +323,12 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
       );
       headers['content-range'] =
           'bytes $offset-${min<int>(((offset + CHUNK_SIZE) - 1), size)}/$size';
-      res = await call(
+      res = await call(CallParams(
         HttpMethod.post,
-        path: path,
+        path,
         headers: headers,
         params: params,
-      );
+      ));
       offset += CHUNK_SIZE;
       if (offset < size) {
         headers['x-appwrite-id'] = res.data['\$id'];
@@ -393,138 +372,27 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin {
     });
   }
 
-  Future<Response> send(
-    HttpMethod method, {
-    String path = '',
-    Map<String, String> headers = const {},
-    Map<String, dynamic> params = const {},
-    ResponseType? responseType,
-    String cacheModel = '',
-    String cacheKey = '',
-    String cacheResponseIdKey = '',
-    String cacheResponseContainerKey = '',
-    Map<String, Object?>? previous,
-  }) async {
-    while (!_initialized && _initProgress) {
-      await Future.delayed(Duration(milliseconds: 10));
-    }
-    if (!_initialized) {
-      await init();
+  @override
+  Future<Response> call(CallParams params) async {
+    while (!_initialized) {
+      await Future.delayed(Duration(milliseconds: 100));
     }
 
-    final uri = Uri.parse(_endPoint + path);
-    http.BaseRequest request = prepareRequest(
-      method,
-      uri: uri,
-      headers: {..._headers!, ...headers},
-      params: params,
+    params.headers.addAll(this._headers!);
+    final response = await _handler.handleCall(
+      withOfflinePersistency(
+        withEndpoint(params, endPoint),
+        getOfflinePersistency(),
+      ),
     );
 
-    try {
-      request = await _interceptRequest(request);
-      final streamedResponse = await _httpClient.send(request);
-      http.Response res = await toResponse(streamedResponse);
-      res = await _interceptResponse(res);
-
-      final response = prepareResponse(
-        res,
-        responseType: responseType,
-      );
-
-      return response;
-    } catch (e) {
-      if (e is AppwriteException) {
-        rethrow;
-      }
-      throw AppwriteException(e.toString());
-    }
+    return response;
   }
 
   @override
-  Future<Response> call(
-    HttpMethod method, {
-    String path = '',
-    Map<String, String> headers = const {},
-    Map<String, dynamic> params = const {},
-    ResponseType? responseType,
-    String cacheModel = '',
-    String cacheKey = '',
-    String cacheResponseIdKey = '',
-    String cacheResponseContainerKey = '',
-    Map<String, Object?>? previous,
-  }) async {
-    while (true) {
-      final uri = Uri.parse(endPoint + path);
-
-      http.BaseRequest request = prepareRequest(
-        method,
-        uri: uri,
-        headers: {..._headers!, ...headers},
-        params: params,
-      );
-
-      if (getOfflinePersistency() && !isOnline.value) {
-        await checkOnlineStatus();
-      }
-
-      if (cacheModel.isNotEmpty &&
-          getOfflinePersistency() &&
-          !isOnline.value &&
-          responseType != ResponseType.bytes) {
-        return handleOfflineRequest(
-          uri: uri,
-          method: method,
-          call: call,
-          path: path,
-          headers: headers,
-          params: params,
-          responseType: responseType,
-          cacheModel: cacheModel,
-          cacheKey: cacheKey,
-          cacheResponseIdKey: cacheResponseIdKey,
-          cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-      }
-
-      try {
-        final response = await send(
-          method,
-          path: path,
-          headers: headers,
-          params: params,
-          responseType: responseType,
-          cacheModel: cacheModel,
-          cacheKey: cacheKey,
-          cacheResponseIdKey: cacheResponseIdKey,
-          cacheResponseContainerKey: cacheResponseContainerKey,
-        );
-
-        if (getOfflinePersistency()) {
-          cacheResponse(
-            cacheModel: cacheModel,
-            cacheKey: cacheKey,
-            cacheResponseIdKey: cacheResponseIdKey,
-            request: request,
-            response: response,
-          );
-        }
-
-        return response;
-      } on AppwriteException catch (e) {
-        if ((e.message != "Network is unreachable" &&
-                !(e.message?.contains("Failed host lookup") ?? false)) ||
-            !getOfflinePersistency()) {
-          rethrow;
-        }
-        isOnline.value = false;
-      } on SocketException catch (_) {
-        if (!getOfflinePersistency()) {
-          rethrow;
-        }
-        isOnline.value = false;
-      } catch (e) {
-        throw AppwriteException(e.toString());
-      }
-    }
+  ClientBase addHandler(CallHandler handler) {
+    handler.setNext(_handler);
+    _handler = handler;
+    return this;
   }
 }
diff --git a/lib/src/client_mixin.dart b/lib/src/client_mixin.dart
index 82fede58..d9926b29 100644
--- a/lib/src/client_mixin.dart
+++ b/lib/src/client_mixin.dart
@@ -1,8 +1,10 @@
+import 'dart:convert';
+
 import 'package:http/http.dart' as http;
+
+import 'enums.dart';
 import 'exception.dart';
 import 'response.dart';
-import 'dart:convert';
-import 'enums.dart';
 
 class ClientMixin {
   http.BaseRequest prepareRequest(
@@ -39,7 +41,7 @@ class ClientMixin {
       }
     } else if (method == HttpMethod.get) {
       if (params.isNotEmpty) {
-        params = params.map((key, value){
+        params = params.map((key, value) {
           if (value is int || value is double) {
             return MapEntry(key, value.toString());
           }
@@ -96,21 +98,26 @@ class ClientMixin {
         data = res.body;
       }
     }
-    return Response(data: data);
+    return Response(headers: res.headers, data: data);
   }
 
-  Future<http.Response> toResponse(http.StreamedResponse streamedResponse) async {
-    if(streamedResponse.statusCode == 204) {
-        return http.Response('',
-          streamedResponse.statusCode,
-          headers: streamedResponse.headers.map((k,v) => k.toLowerCase()=='content-type' ? MapEntry(k, 'text/plain') : MapEntry(k,v)),
-          request: streamedResponse.request,
-          isRedirect: streamedResponse.isRedirect,
-          persistentConnection: streamedResponse.persistentConnection,
-          reasonPhrase: streamedResponse.reasonPhrase,
-        );
-      } else {
-        return await http.Response.fromStream(streamedResponse);
-      }
+  Future<http.Response> toResponse(
+      http.StreamedResponse streamedResponse) async {
+    if (streamedResponse.statusCode == 204) {
+      return http.Response(
+        '',
+        streamedResponse.statusCode,
+        headers: streamedResponse.headers.map((k, v) =>
+            k.toLowerCase() == 'content-type'
+                ? MapEntry(k, 'text/plain')
+                : MapEntry(k, v)),
+        request: streamedResponse.request,
+        isRedirect: streamedResponse.isRedirect,
+        persistentConnection: streamedResponse.persistentConnection,
+        reasonPhrase: streamedResponse.reasonPhrase,
+      );
+    } else {
+      return await http.Response.fromStream(streamedResponse);
+    }
   }
 }
diff --git a/lib/src/client_offline_mixin.dart b/lib/src/client_offline_mixin.dart
index b1a1d4eb..7cf56466 100644
--- a/lib/src/client_offline_mixin.dart
+++ b/lib/src/client_offline_mixin.dart
@@ -5,8 +5,11 @@ import 'package:flutter/foundation.dart';
 import 'package:http/http.dart' as http;
 import 'package:sembast/utils/value_utils.dart';
 
+import 'call_handlers/offline_call_handler.dart';
+import 'call_params.dart';
 import 'enums.dart';
 import 'exception.dart';
+import 'offline/caller.dart';
 import 'offline/services/accessed_at.dart';
 import 'offline/services/cache_size.dart';
 import 'offline/services/model_data.dart';
@@ -25,18 +28,7 @@ class ClientOfflineMixin {
   late QueuedWrites _queuedWrites;
 
   Future<void> initOffline({
-    required Future<Response<dynamic>> Function(
-      HttpMethod, {
-      String path,
-      Map<String, String> headers,
-      Map<String, dynamic> params,
-      ResponseType? responseType,
-      String cacheModel,
-      String cacheKey,
-      String cacheResponseIdKey,
-      String cacheResponseContainerKey,
-    })
-        call,
+    required Caller call,
     void Function(Object)? onWriteQueueError,
     required int Function() getOfflineCacheSize,
   }) async {
@@ -64,20 +56,7 @@ class ClientOfflineMixin {
     _queuedWrites = QueuedWrites(db);
   }
 
-  Future<void> processWriteQueue(
-      Future<Response<dynamic>> Function(
-    HttpMethod, {
-    String path,
-    Map<String, String> headers,
-    Map<String, dynamic> params,
-    ResponseType? responseType,
-    String cacheModel,
-    String cacheKey,
-    String cacheResponseIdKey,
-    String cacheResponseContainerKey,
-  })
-          call,
-      {void Function(Object e)? onError}) async {
+  Future<void> processWriteQueue(Caller call, {Function? onError}) async {
     if (!isOnline.value) return;
     final queuedWrites = await _queuedWrites.list();
     for (final queuedWrite in queuedWrites) {
@@ -85,16 +64,22 @@ class ClientOfflineMixin {
         final method = HttpMethod.values
             .where((v) => v.name() == queuedWrite.method)
             .first;
-        final res = await call(
-          method,
-          path: queuedWrite.path,
-          headers: queuedWrite.headers,
-          params: queuedWrite.params,
-          cacheModel: queuedWrite.cacheModel,
-          cacheKey: queuedWrite.cacheKey,
-          cacheResponseContainerKey: queuedWrite.cacheResponseContainerKey,
-          cacheResponseIdKey: queuedWrite.cacheResponseIdKey,
+
+        final params = withCacheParams(
+          CallParams(
+            method,
+            queuedWrite.path,
+            headers: queuedWrite.headers,
+            params: queuedWrite.params,
+          ),
+          CacheParams(
+            model: queuedWrite.cacheModel,
+            key: queuedWrite.cacheKey,
+            responseContainerKey: queuedWrite.cacheResponseContainerKey,
+            responseIdKey: queuedWrite.cacheResponseIdKey,
+          ),
         );
+        final res = await call(params);
 
         if (method == HttpMethod.post) {
           await _modelData.upsert(
@@ -160,16 +145,7 @@ class ClientOfflineMixin {
   Future<Response> handleOfflineRequest({
     required Uri uri,
     required HttpMethod method,
-    required Future<Response<dynamic>> Function(HttpMethod,
-            {String path,
-            Map<String, String> headers,
-            Map<String, dynamic> params,
-            ResponseType? responseType,
-            String cacheModel,
-            String cacheKey,
-            String cacheResponseIdKey,
-            String cacheResponseContainerKey})
-        call,
+    required Caller call,
     String path = '',
     Map<String, String> headers = const {},
     Map<String, dynamic> params = const {},
@@ -320,13 +296,13 @@ class ClientOfflineMixin {
         }
 
         try {
-          final res = await call(
+          final res = await call(CallParams(
             method,
+            path,
             headers: headers,
             params: params,
-            path: path,
             responseType: responseType,
-          );
+          ));
 
           final futures = <Future>[];
           if (method == HttpMethod.post) {
@@ -390,45 +366,122 @@ class ClientOfflineMixin {
     return completer.future;
   }
 
+  void cacheRelated({
+    required Map<String, Object?> document,
+  }) {
+    // iterate over each attribute to see if it's nested data
+    document.entries.forEach((entry) {
+      if (entry.value is Map) {
+        final nestedDocument = entry.value as Map<String, Object?>;
+        final nestedDatabaseId = nestedDocument['\$databaseId'] as String?;
+        final nestedCollectionId = nestedDocument['\$collectionId'] as String?;
+        final nestedDocumentId = nestedDocument['\$id'] as String?;
+        if (nestedDatabaseId == null ||
+            nestedCollectionId == null ||
+            nestedDocumentId == null) return;
+        final nestedModel =
+            "/databases/$nestedDatabaseId/collections/$nestedCollectionId/documents";
+        cacheResponse(
+          cacheModel: nestedModel,
+          cacheKey: nestedDocumentId,
+          cacheResponseIdKey: "\$id",
+          cacheResponseContainerKey: '',
+          requestMethod: 'GET',
+          responseData: entry.value,
+        );
+        document[entry.key] = {
+          '\$id': nestedDocumentId,
+          '\$databaseId': nestedDatabaseId,
+          '\$collectionId': nestedCollectionId,
+        };
+      } else if (entry.value is List &&
+          entry.value != null &&
+          (entry.value as List).isNotEmpty) {
+        final values = (entry.value as List);
+        if (!(values.first is Map<String, Object?>)) return;
+        final nestedDocument = values.first;
+        final nestedDatabaseId = nestedDocument['\$databaseId'] as String?;
+        final nestedCollectionId = nestedDocument['\$collectionId'] as String?;
+        if (nestedDatabaseId == null || nestedCollectionId == null) return;
+        final nestedModel =
+            "/databases/$nestedDatabaseId/collections/$nestedCollectionId/documents";
+        cacheResponse(
+          cacheModel: nestedModel,
+          cacheKey: '',
+          cacheResponseIdKey: "\$id",
+          cacheResponseContainerKey: 'documents',
+          requestMethod: 'GET',
+          responseData: {
+            'documents': entry.value,
+          },
+        );
+        document[entry.key] = values.map((value) {
+          final nestedDocumentId = value['\$id'] as String?;
+          return {
+            '\$id': nestedDocumentId,
+            '\$databaseId': nestedDatabaseId,
+            '\$collectionId': nestedCollectionId,
+          };
+        }).toList();
+      }
+    });
+  }
+
+  Future<void> cacheCollections(List<Map<String, dynamic>> relations) {
+    final futures = <Future>[];
+    for (var collection in relations) {
+      futures.add(_modelData.cacheModel(collection));
+    }
+    return Future.wait(futures);
+  }
+
   void cacheResponse({
     required String cacheModel,
     required String cacheKey,
     required String cacheResponseIdKey,
-    required http.BaseRequest request,
-    required Response response,
+    required String cacheResponseContainerKey,
+    required String requestMethod,
+    required dynamic responseData,
   }) {
     if (cacheModel.isEmpty) return;
 
-    switch (request.method) {
+    switch (requestMethod) {
       case 'GET':
-        final clone = cloneMap(response.data);
+        final clone = cloneMap(responseData);
         if (cacheKey.isNotEmpty) {
+          cacheRelated(document: clone);
           _modelData.upsert(model: cacheModel, data: clone, key: cacheKey);
         } else {
-          clone.forEach((key, value) {
-            if (key == 'total') return;
-            _modelData.batchUpsert(
-              model: cacheModel,
-              dataList: value as List,
-              idKey: cacheResponseIdKey,
-            );
+          final values = clone[cacheResponseContainerKey] as List;
+          values.forEach((value) {
+            cacheRelated(document: value);
           });
+          _modelData.batchUpsert(
+            model: cacheModel,
+            dataList: values,
+            idKey: cacheResponseIdKey,
+          );
         }
         break;
       case 'POST':
       case 'PUT':
       case 'PATCH':
-        Map<String, Object?> clone = cloneMap(response.data);
+        Map<String, Object?> clone = cloneMap(responseData);
         if (cacheKey.isEmpty) {
           cacheKey = clone['\$id'] as String;
         }
         if (cacheModel.endsWith('/prefs')) {
-          clone = response.data['prefs'];
+          clone = responseData['prefs'];
         }
         _modelData.upsert(model: cacheModel, data: clone, key: cacheKey);
         break;
       case 'DELETE':
         if (cacheKey.isNotEmpty) {
+          // _modelData.get(model: cacheModel, key: cacheKey).then((cachedData) {
+          //   if (cachedData == null) {
+          //     return;
+          //   }
+          // });
           _modelData.delete(model: cacheModel, key: cacheKey);
         }
     }
diff --git a/lib/src/offline/caller.dart b/lib/src/offline/caller.dart
new file mode 100644
index 00000000..8eb6a0f9
--- /dev/null
+++ b/lib/src/offline/caller.dart
@@ -0,0 +1,4 @@
+import '../call_params.dart';
+import '../response.dart';
+
+typedef Caller = Future<Response<dynamic>> Function(CallParams params);
diff --git a/lib/src/offline/route.dart b/lib/src/offline/route.dart
new file mode 100644
index 00000000..ad008a1e
--- /dev/null
+++ b/lib/src/offline/route.dart
@@ -0,0 +1,47 @@
+class Route {
+  String method = '';
+  String path;
+  final List<String> _aliases = [];
+  final Map<String, dynamic> labels = {};
+
+  final Map<String, int> _pathParams = {};
+
+  Route(this.method, this.path) : super();
+
+  List<String> get aliases => _aliases;
+  Map<String, int> get pathParams => _pathParams;
+
+  Route alias(String path) {
+    if (!_aliases.contains(path)) {
+      _aliases.add(path);
+    }
+
+    return this;
+  }
+
+  void setPathParam(String key, int index) {
+    _pathParams[key] = index;
+  }
+
+  Map<String, String> getPathValues(String path) {
+    var pathValues = <String, String>{};
+    var parts = path.split('/').where((part) => part.isNotEmpty);
+
+    for (var entry in pathParams.entries) {
+      if (entry.value < parts.length) {
+        pathValues[entry.key] = parts.elementAt(entry.value);
+      }
+    }
+
+    return pathValues;
+  }
+
+  Route label(String key, String value) {
+    labels[key] = value;
+    return this;
+  }
+
+  String? getLabel(String key, {String? defaultValue}) {
+    return labels[key] ?? defaultValue;
+  }
+}
diff --git a/lib/src/offline/route_mapping.dart b/lib/src/offline/route_mapping.dart
new file mode 100644
index 00000000..81784f70
--- /dev/null
+++ b/lib/src/offline/route_mapping.dart
@@ -0,0 +1,80 @@
+final routes = [
+  {
+    "method": "get",
+    "path": "\/account",
+    "offline": {
+      "model": "\/account",
+      "key": "current",
+      "response-key": "\$id",
+      "container-key": "",
+    },
+  },
+  {
+    "method": "post",
+    "path": "\/account\/sessions\/email",
+    "offline": {
+      "model": "",
+      "key": "",
+      "response-key": "\$id",
+      "container-key": "",
+    },
+  },
+  {
+    "method": "get",
+    "path": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
+    "offline": {
+      "model":
+          "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
+      "key": "",
+      "response-key": "\$id",
+      "container-key": "documents",
+    },
+  },
+  {
+    "method": "post",
+    "path": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
+    "offline": {
+      "model":
+          "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
+      "key": "",
+      "response-key": "\$id",
+      "container-key": "",
+    },
+  },
+  {
+    "method": "get",
+    "path":
+        "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}",
+    "offline": {
+      "model":
+          "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
+      "key": "{documentId}",
+      "response-key": "\$id",
+      "container-key": "",
+    },
+  },
+  {
+    "method": "patch",
+    "path":
+        "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}",
+    "offline": {
+      "model":
+          "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
+      "key": "{documentId}",
+      "response-key": "\$id",
+      "container-key": "",
+    },
+  },
+  {
+    "method": "delete",
+    "path":
+        "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}",
+    "offline": {
+      "model":
+          "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
+      "key": "{documentId}",
+      "response-key": "\$id",
+      "container-key": "",
+    },
+  }
+];
diff --git a/lib/src/offline/router.dart b/lib/src/offline/router.dart
new file mode 100644
index 00000000..7720ddff
--- /dev/null
+++ b/lib/src/offline/router.dart
@@ -0,0 +1,126 @@
+import 'dart:collection';
+
+import 'route.dart';
+
+class Router {
+  static const String placeholderToken = ':::';
+
+  Map<String, Map<String, Route>> _routes = {
+    'GET': {},
+    'POST': {},
+    'PUT': {},
+    'PATCH': {},
+    'DELETE': {},
+  };
+
+  List<int> _params = [];
+
+  UnmodifiableMapView<String, Map<String, Route>> getRoutes() {
+    return UnmodifiableMapView(_routes);
+  }
+
+  void addRoute(Route route) {
+    List<dynamic> result = preparePath(route.path);
+    String path = result[0];
+    Map<String, int> params = result[1];
+
+    if (!_routes.containsKey(route.method)) {
+      throw Exception("Method (${route.method}) not supported.");
+    }
+
+    if (_routes[route.method]!.containsKey(path)) {
+      throw Exception("Route for (${route.method}:$path) already registered.");
+    }
+
+    params.forEach((key, index) {
+      route.setPathParam(key, index);
+    });
+
+    _routes[route.method]![path] = (route);
+
+    for (String alias in route.aliases) {
+      List<dynamic> aliasResult = preparePath(alias);
+      String aliasPath = aliasResult[0];
+      _routes[route.method]![aliasPath] = route;
+    }
+  }
+
+  Route? match(String method, String path) {
+    if (!_routes.containsKey(method)) {
+      return null;
+    }
+
+    List<String> parts = path.split('/').where((p) => p.isNotEmpty).toList();
+    int length = parts.length - 1;
+    List<int> filteredParams = _params.where((i) => i <= length).toList();
+
+    for (List<int> sample in combinations<int>(filteredParams)) {
+      sample = sample.where((i) => i <= length).toList();
+      String match = parts
+          .asMap()
+          .entries
+          .map(
+            (entry) =>
+                sample.contains(entry.key) ? placeholderToken : entry.value,
+          )
+          .join('/');
+
+      if (_routes[method]!.containsKey(match)) {
+        return _routes[method]![match]!;
+      }
+    }
+
+    return null;
+  }
+
+  Iterable<List<T>> combinations<T>(List<T> set) {
+    final result = <List<T>>[[]];
+
+    for (final element in set) {
+      final newCombinations = <List<T>>[];
+      for (final combination in result) {
+        final ret = [element, ...combination];
+        newCombinations.add(ret);
+      }
+      result.addAll(newCombinations);
+    }
+
+    return result;
+  }
+
+  List<dynamic> preparePath(String path) {
+    List<String> parts = path.split('/').where((p) => p.isNotEmpty).toList();
+    String prepare = '';
+    Map<String, int> params = {};
+
+    for (int key = 0; key < parts.length; key++) {
+      String part = parts[key];
+      if (key != 0) {
+        prepare += '/';
+      }
+
+      if (part.startsWith('{') && part.endsWith('}')) {
+        prepare += placeholderToken;
+        params[part.substring(1, part.length - 1)] = key;
+        if (!_params.contains(key)) {
+          _params.add(key);
+        }
+      } else {
+        prepare += part;
+      }
+    }
+
+    return [prepare, params];
+  }
+
+  void reset() {
+    _params = [];
+    _routes = {
+      'GET': {},
+      'POST': {},
+      'PUT': {},
+      'PATCH': {},
+      'DELETE': {},
+    };
+  }
+}
diff --git a/lib/src/offline/services/cache_size.dart b/lib/src/offline/services/cache_size.dart
index cf1b0c87..0934179f 100644
--- a/lib/src/offline/services/cache_size.dart
+++ b/lib/src/offline/services/cache_size.dart
@@ -19,23 +19,33 @@ class CacheSize {
     return encoded;
   }
 
-  Future<void> applyChange(int change) async {
-    if (change == 0) return;
+  Future<int?> applyChange(Transaction txn, int change) async {
+    if (change == 0) return null;
 
     final record = getCacheSizeRecordRef();
-
-    final currentSize = await record.get(_db) ?? 0;
-    await record.put(_db, currentSize + change);
+    final currentSize = await record.get(txn) ?? 0;
+    return await record.put(txn, currentSize + change);
   }
 
   Future<void> update({
-    Map<String, dynamic>? oldData,
+    required RecordRef<String, Map<String, Object?>> recordRef,
+    required Transaction txn,
     Map<String, dynamic>? newData,
   }) async {
+    final oldData = await recordRef.get(txn);
     final oldSize = oldData != null ? encode(oldData).length : 0;
     final newSize = newData != null ? encode(newData).length : 0;
     final change = newSize - oldSize;
-    await applyChange(change);
+    final cacheSize = await applyChange(txn, change);
+
+    if (change != 0) {
+      print([
+        '${recordRef.key}: oldSize: $oldSize',
+        'newSize: $newSize',
+        'change: $change',
+        'cacheSize: $cacheSize',
+      ].join(', '));
+    }
   }
 
   void onChange(void callback(int? currentSize)) {
diff --git a/lib/src/offline/services/model_data.dart b/lib/src/offline/services/model_data.dart
index fbda4788..a158278e 100644
--- a/lib/src/offline/services/model_data.dart
+++ b/lib/src/offline/services/model_data.dart
@@ -1,5 +1,6 @@
 import 'package:appwrite/src/offline/services/cache_size.dart';
 import 'package:sembast/sembast.dart';
+import 'package:sembast/utils/value_utils.dart';
 
 import '../../../appwrite.dart';
 import 'accessed_at.dart';
@@ -8,6 +9,9 @@ class ModelData {
   final Database _db;
   final AccessedAt _accessedAt;
   final CacheSize _cacheSize;
+  final int maxDepth = 3;
+  final documentModelRegex = RegExp(
+      r'^/databases/([a-zA-Z0-9\-]*)/collections/([a-zA-Z0-9\-]*)/documents$');
 
   ModelData(this._db)
       : _accessedAt = AccessedAt(_db),
@@ -17,15 +21,128 @@ class ModelData {
     return stringMapStoreFactory.store(model);
   }
 
+  Future<void> cacheModel(Map<String, dynamic> collection) {
+    final store = stringMapStoreFactory.store('collections');
+
+    return store
+        .record("${collection['databaseId']}|${collection['collectionId']}")
+        .put(_db, collection);
+  }
+
   Future<Map<String, dynamic>?> get({
     required String model,
     required String key,
+  }) async {
+    final immutableRecord = await _getRecord(model: model, key: key);
+
+    if (immutableRecord == null) return null;
+
+    final record = cloneMap(immutableRecord);
+    await _populateRelated(record, 0);
+
+    return record;
+  }
+
+  Future<Map<String, dynamic>?> _getRecord({
+    required String model,
+    required String key,
   }) async {
     final store = getModelStore(model);
     final recordRef = store.record(key);
     return recordRef.get(_db);
   }
 
+  bool _isNestedDocument(Map<String, dynamic> record) {
+    return record.containsKey('\$databaseId') &&
+        record.containsKey('\$collectionId') &&
+        record.containsKey('\$id');
+  }
+
+  /// Given a record with $databaseId, $collectionId and $id, populate the rest
+  /// of the attributes from the cache and then populate the related records.
+  Future<Map<String, dynamic>?> _populateRecord(
+      Map<String, dynamic>? record, int depth) async {
+    if (record == null) return record;
+
+    if (!_isNestedDocument(record)) return record;
+
+    final databaseId = record['\$databaseId'] as String;
+    final collectionId = record['\$collectionId'] as String;
+    final documentId = record['\$id'] as String;
+
+    final nestedModel =
+        "/databases/$databaseId/collections/$collectionId/documents";
+
+    final cached = await _getRecord(
+      model: nestedModel,
+      key: documentId,
+    );
+
+    if (cached == null) return record;
+
+    record.addAll(cloneMap(cached));
+
+    await _populateRelated(record, depth + 1);
+
+    return record;
+  }
+
+  /// Iterate over every attribute of a record and fetch related records from
+  /// the cache.
+  Future<void> _populateRelated(Map<String, dynamic>? record, int depth) {
+    if (record == null) {
+      return Future.value();
+    }
+
+    // iterate over each attribute and check if it is a relation
+    final futures = <Future>[];
+    for (final attribute in record.entries) {
+      if (attribute.value is Map<String, Object?>) {
+        final map = attribute.value as Map<String, Object?>;
+        if (_isNestedDocument(record)) {
+          if (depth >= maxDepth) {
+            record[attribute.key] = null;
+          } else {
+            final future = _populateRecord(map, depth).then((populated) {
+              record[attribute.key] = populated;
+            });
+
+            futures.add(future);
+          }
+        }
+      } else if (attribute.value is List) {
+        final List list = attribute.value as List;
+        final futureList = <Future<Map<String, dynamic>?>>[];
+        if (list.isEmpty) continue;
+
+        if (depth >= maxDepth &&
+            list.first is Map<String, Object?> &&
+            _isNestedDocument(list.first)) {
+          record[attribute.key] = [];
+          continue;
+        }
+
+        for (final map in list) {
+          if (map is! Map<String, Object?>) {
+            continue;
+          }
+          futureList.add(_populateRecord(map, depth));
+        }
+
+        if (futureList.isEmpty) {
+          continue;
+        }
+
+        final future = Future.wait(futureList).then((populated) {
+          record[attribute.key] = populated;
+        });
+
+        futures.add(future);
+      }
+    }
+    return Future.wait(futures);
+  }
+
   Future<Map<String, dynamic>> list({
     required String model,
     required String cacheResponseContainerKey,
@@ -50,6 +167,7 @@ class ModelData {
                 final List<Filter> equalFilters = [];
                 value.forEach((v) {
                   equalFilters.add(Filter.equals(q.params[0], v));
+                  equalFilters.add(Filter.equals("${q.params[0]}.\$id", v));
                 });
                 filters.add(Filter.or(equalFilters));
               });
@@ -82,6 +200,30 @@ class ModelData {
             filters.add(Filter.matches(q.params[0], r'${q.params[1]}+'));
             break;
 
+          case 'isNull':
+            // TODO: Handle this case.
+            break;
+
+          case 'isNotNull':
+            // TODO: Handle this case.
+            break;
+
+          case 'between':
+            // TODO: Handle this case.
+            break;
+
+          case 'startsWith':
+            // TODO: Handle this case.
+            break;
+
+          case 'endsWith':
+            // TODO: Handle this case.
+            break;
+
+          case 'select':
+            // TODO: Handle this case.
+            break;
+
           case 'orderAsc':
             sortOrders.add(SortOrder(q.params[0] as String));
             break;
@@ -116,19 +258,27 @@ class ModelData {
     final records = await store.find(_db, finder: finder);
     final count = await store.count(_db, filter: filter);
 
+    final list = records.map((record) {
+      // convert to Map<String, dynamic>
+      final map = Map<String, dynamic>();
+      record.value.entries.forEach((entry) {
+        map[entry.key] = entry.value;
+      });
+      return map;
+    }).toList();
+
+    final futures = <Future<Map<String, dynamic>?>>[];
+    for (final record in list) {
+      futures.add(_populateRecord(record, 0));
+    }
+
     final keys = records.map((record) => record.key).toList();
 
     _accessedAt.update(model: store.name, keys: keys);
 
     return {
       'total': count,
-      cacheResponseContainerKey: records.map((record) {
-        final map = Map<String, dynamic>();
-        record.value.entries.forEach((entry) {
-          map[entry.key] = entry.value;
-        });
-        return map;
-      }).toList(),
+      cacheResponseContainerKey: await Future.wait(futures),
     };
   }
 
@@ -137,13 +287,90 @@ class ModelData {
     required Map<String, dynamic> data,
     required String key,
   }) async {
-    final store = getModelStore(model);
+    final match = documentModelRegex.firstMatch(model);
+    if (match?.groupCount == 2) {
+      // data is a document
+
+      // match starting at 1 since 0 is the full match
+      final databaseId = match!.group(1)!;
+      final collectionId = match.group(2)!;
+
+      final collectionStore = getModelStore('collections');
+      final recordRef = collectionStore.record('$databaseId|$collectionId');
+      final collection = await recordRef.get(_db);
+      final attributes = (collection?['attributes'] ?? <Map<String, Object?>>{})
+          as Map<String, Object?>;
+      for (final attributeEntry in attributes.entries) {
+        final key = attributeEntry.key;
+        final attribute = attributeEntry.value as Map<String, Object?>;
+        final relatedCollection = attribute['relatedCollection'] as String;
+        final relationType = attribute['relationType'] as String;
+        final side = attribute['side'] as String;
+
+        if (!data.containsKey(key)) continue;
+
+        final nestedModel =
+            "/databases/$databaseId/collections/$relatedCollection/documents";
+
+        if (relationType == 'oneToOne' ||
+            (relationType == 'oneToMany' && side == 'child') ||
+            (relationType == 'manyToOne' && side == 'parent')) {
+          // data[key] is a single document
+          String documentId = '';
+          if (data[key] is String) {
+            // data[key] is a document ID
+            documentId = data[key] as String;
+          } else if (data[key] is Map<String, Object?>) {
+            // data[key] is a nested document
+            final related = data[key] as Map<String, Object?>;
+            documentId = (related['\$id'] ?? ID.unique()) as String;
+            await upsert(model: nestedModel, key: documentId, data: related);
+          }
+          data[key] = {
+            '\$databaseId': databaseId,
+            '\$collectionId': relatedCollection,
+            '\$id': documentId
+          };
+        } else {
+          // data[key] is a list of documents
+          final result = <Map<String, Object?>>[];
+          final relatedList = data[key] as List;
+          for (final related in relatedList) {
+            String documentId = '';
+            if (related is String) {
+              // related is a document ID
+              documentId = related;
+            } else if (related is Map<String, Object?>) {
+              // related is a nested document
+              documentId = (related['\$id'] ?? ID.unique()) as String;
+              await upsert(model: nestedModel, key: documentId, data: related);
+            }
+            result.add({
+              '\$databaseId': databaseId,
+              '\$collectionId': relatedCollection,
+              '\$id': documentId
+            });
+          }
+          data[key] = result;
+        }
+      }
+    }
 
-    final recordRef = store.record(key);
-    final record = await recordRef.get(_db);
-    _cacheSize.update(oldData: record, newData: data);
+    final result = await _db.transaction((txn) async {
+      final store = getModelStore(model);
+
+      final recordRef = store.record(key);
+      final oldData = await recordRef.get(txn);
+      final oldSize = oldData != null ? _cacheSize.encode(oldData).length : 0;
 
-    final result = await recordRef.put(_db, data, merge: true);
+      final result = await recordRef.put(txn, data, merge: true);
+
+      final newSize = _cacheSize.encode(result).length;
+      final change = newSize - oldSize;
+
+      await _cacheSize.applyChange(txn, change);
+      return result;
+    });
     await _accessedAt.update(model: model, keys: [key]);
     return result;
   }
@@ -167,16 +394,19 @@ class ModelData {
   Future<void> delete({required String model, required String key}) async {
     final store = getModelStore(model);
     RecordSnapshot<String, Map<String, Object?>>? record;
+    final recordRef = store.record(key);
 
-    record = await store.record(key).getSnapshot(_db);
+    record = await recordRef.getSnapshot(_db);
 
     if (record == null) {
       return;
     }
 
-    _cacheSize.update(oldData: record.value);
-
-    await record.ref.delete(_db);
+    await _db.transaction((txn) async {
+      final oldSize = _cacheSize.encode(record!.value).length;
+      await _cacheSize.applyChange(txn, oldSize * -1);
+      await record.ref.delete(_db);
+    });
 
     await _accessedAt.delete(model: model, key: record.key);
   }
diff --git a/lib/src/offline_db_io.dart b/lib/src/offline_db_io.dart
index 61b36422..d6cf5481 100644
--- a/lib/src/offline_db_io.dart
+++ b/lib/src/offline_db_io.dart
@@ -3,7 +3,7 @@ import 'dart:io';
 import 'package:sembast/sembast.dart';
 import 'package:sembast_sqflite/sembast_sqflite.dart';
 import 'package:sqflite/sqflite.dart' as sqflite;
-import 'package:sqflite_common_ffi/sqflite_ffi.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart' hide Database;
 
 class OfflineDatabase {
   static final OfflineDatabase instance = OfflineDatabase._internal();
diff --git a/lib/src/response.dart b/lib/src/response.dart
index bf4eea3f..306bbff3 100644
--- a/lib/src/response.dart
+++ b/lib/src/response.dart
@@ -1,8 +1,9 @@
 import 'dart:convert';
 
 class Response<T> {
-  Response({this.data});
+  Response({this.headers = const {}, this.data});
 
+  final Map<String, String> headers;
   T? data;
 
   @override