diff --git a/packages/wyatt_http_client/example/http_client_fastapi_example.dart b/packages/wyatt_http_client/example/http_client_fastapi_example.dart
new file mode 100644
index 00000000..21c13b42
--- /dev/null
+++ b/packages/wyatt_http_client/example/http_client_fastapi_example.dart
@@ -0,0 +1,386 @@
+// Copyright (C) 2022 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+// ignore_for_file: public_member_api_docs, sort_constructors_first
+import 'dart:convert';
+
+import 'package:wyatt_http_client/src/authentication/refresh_token_client.dart';
+import 'package:wyatt_http_client/src/rest_client.dart';
+import 'package:wyatt_http_client/src/utils/protocols.dart';
+
+enum EmailVerificationAction {
+ signUp,
+ resetPassword,
+ changeEmail;
+
+ String toSnakeCase() {
+ return name.splitMapJoin(
+ RegExp('[A-Z]'),
+ onMatch: (m) => '_${m[0]?.toLowerCase()}',
+ onNonMatch: (n) => n,
+ );
+ }
+
+ factory EmailVerificationAction.fromString(String str) {
+ return EmailVerificationAction.values.firstWhere(
+ (EmailVerificationAction element) => element.toSnakeCase() == str,
+ );
+ }
+}
+
+class VerifyCode {
+ final String email;
+ final String verificationCode;
+ final EmailVerificationAction action;
+ VerifyCode({
+ required this.email,
+ required this.verificationCode,
+ required this.action,
+ });
+
+ VerifyCode copyWith({
+ String? email,
+ String? verificationCode,
+ EmailVerificationAction? action,
+ }) {
+ return VerifyCode(
+ email: email ?? this.email,
+ verificationCode: verificationCode ?? this.verificationCode,
+ action: action ?? this.action,
+ );
+ }
+
+ Map toMap() {
+ return {
+ 'email': email,
+ 'verification_code': verificationCode,
+ 'action': action.toSnakeCase(),
+ };
+ }
+
+ factory VerifyCode.fromMap(Map map) {
+ return VerifyCode(
+ email: map['email'] as String,
+ verificationCode: map['verification_code'] as String,
+ action: EmailVerificationAction.fromString(map['action'] as String),
+ );
+ }
+
+ String toJson() => json.encode(toMap());
+
+ factory VerifyCode.fromJson(String source) =>
+ VerifyCode.fromMap(json.decode(source) as Map);
+
+ @override
+ String toString() =>
+ 'VerifyCode(email: $email, verificationCode: $verificationCode, action: $action)';
+}
+
+class Account {
+ final String email;
+ final String? sessionId;
+ Account({
+ required this.email,
+ this.sessionId,
+ });
+
+ Account copyWith({
+ String? email,
+ String? sessionId,
+ }) {
+ return Account(
+ email: email ?? this.email,
+ sessionId: sessionId ?? this.sessionId,
+ );
+ }
+
+ Map toMap() {
+ return {
+ 'email': email,
+ 'session_id': sessionId,
+ };
+ }
+
+ factory Account.fromMap(Map map) {
+ return Account(
+ email: map['email'] as String,
+ sessionId: map['session_id'] != null ? map['session_id'] as String : null,
+ );
+ }
+
+ String toJson() => json.encode(toMap());
+
+ factory Account.fromJson(String source) =>
+ Account.fromMap(json.decode(source) as Map);
+
+ @override
+ String toString() => 'Account(email: $email, sessionId: $sessionId)';
+}
+
+class SignUp {
+ final String sessionId;
+ final String password;
+ SignUp({
+ required this.sessionId,
+ required this.password,
+ });
+
+ SignUp copyWith({
+ String? sessionId,
+ String? password,
+ }) {
+ return SignUp(
+ sessionId: sessionId ?? this.sessionId,
+ password: password ?? this.password,
+ );
+ }
+
+ Map toMap() {
+ return {
+ 'session_id': sessionId,
+ 'password': password,
+ };
+ }
+
+ factory SignUp.fromMap(Map map) {
+ return SignUp(
+ sessionId: map['session_id'] as String,
+ password: map['password'] as String,
+ );
+ }
+
+ String toJson() => json.encode(toMap());
+
+ factory SignUp.fromJson(String source) =>
+ SignUp.fromMap(json.decode(source) as Map);
+
+ @override
+ String toString() => 'SignUp(sessionId: $sessionId, password: $password)';
+}
+
+class TokenSuccess {
+ final String accessToken;
+ final String refreshToken;
+ final Account account;
+ TokenSuccess({
+ required this.accessToken,
+ required this.refreshToken,
+ required this.account,
+ });
+
+ TokenSuccess copyWith({
+ String? accessToken,
+ String? refreshToken,
+ Account? account,
+ }) {
+ return TokenSuccess(
+ accessToken: accessToken ?? this.accessToken,
+ refreshToken: refreshToken ?? this.refreshToken,
+ account: account ?? this.account,
+ );
+ }
+
+ Map toMap() {
+ return {
+ 'access_token': accessToken,
+ 'refresh_token': refreshToken,
+ 'account': account.toMap(),
+ };
+ }
+
+ factory TokenSuccess.fromMap(Map map) {
+ return TokenSuccess(
+ accessToken: map['access_token'] as String,
+ refreshToken: map['refresh_token'] as String,
+ account: Account.fromMap(map['account'] as Map),
+ );
+ }
+
+ String toJson() => json.encode(toMap());
+
+ factory TokenSuccess.fromJson(String source) =>
+ TokenSuccess.fromMap(json.decode(source) as Map);
+
+ @override
+ String toString() =>
+ 'TokenSuccess(accessToken: $accessToken, refreshToken: $refreshToken, account: $account)';
+}
+
+class Login {
+ final String email;
+ final String password;
+ Login({
+ required this.email,
+ required this.password,
+ });
+
+ Login copyWith({
+ String? email,
+ String? password,
+ }) {
+ return Login(
+ email: email ?? this.email,
+ password: password ?? this.password,
+ );
+ }
+
+ Map toMap() {
+ return {
+ 'email': email,
+ 'password': password,
+ };
+ }
+
+ factory Login.fromMap(Map map) {
+ return Login(
+ email: map['email'] as String,
+ password: map['password'] as String,
+ );
+ }
+
+ String toJson() => json.encode(toMap());
+
+ factory Login.fromJson(String source) =>
+ Login.fromMap(json.decode(source) as Map);
+
+ @override
+ String toString() => 'Login(email: $email, password: $password)';
+}
+
+class FastAPI {
+ final String baseUrl;
+ final RefreshTokenClient client;
+ final int apiVersion;
+
+ FastAPI({
+ this.baseUrl = 'localhost:80',
+ RefreshTokenClient? client,
+ this.apiVersion = 1,
+ }) : client = client ??
+ RefreshTokenClient(
+ authorizationEndpoint: '',
+ tokenEndpoint: '',
+ accessTokenParser: (body) => body['access_token']! as String,
+ refreshTokenParser: (body) => body['refresh_token']! as String,
+ inner: RestClient(
+ protocol: Protocols.http,
+ authority: baseUrl,
+ ),
+ );
+
+ String get apiPath => '/api/v$apiVersion';
+
+ Future sendSignUpCode(String email) async {
+ final r = await client.post(
+ Uri.parse('$apiPath/auth/send-sign-up-code'),
+ body: {
+ 'email': email,
+ },
+ );
+ if (r.statusCode != 201) {
+ throw Exception('Invalid reponse: ${r.statusCode}');
+ }
+ }
+
+ Future verifyCode(VerifyCode verifyCode) async {
+ final r = await client.post(
+ Uri.parse('$apiPath/auth/verify-code'),
+ body: verifyCode.toMap(),
+ );
+ if (r.statusCode != 202) {
+ throw Exception('Invalid reponse: ${r.statusCode}');
+ } else {
+ return Account.fromMap(
+ (jsonDecode(r.body) as Map)['account']
+ as Map,
+ );
+ }
+ }
+
+ Future signUp(SignUp signUp) async {
+ final r = await client.post(
+ Uri.parse('$apiPath/auth/sign-up'),
+ body: signUp.toMap(),
+ );
+ if (r.statusCode != 201) {
+ throw Exception('Invalid reponse: ${r.statusCode}');
+ } else {
+ return Account.fromJson(r.body);
+ }
+ }
+
+ Future signInWithPassword(Login login) async {
+ final r = await client.authorize(login.toMap());
+ return TokenSuccess.fromJson(r.body);
+ }
+
+ Future refresh() async {
+ final r = await client.refresh();
+ return TokenSuccess.fromJson(r?.body ?? '');
+ }
+
+ Future> getAccountList() async {
+ final r = await client.get(
+ Uri.parse('$apiPath/account'),
+ );
+ if (r.statusCode != 200) {
+ throw Exception('Invalid reponse: ${r.statusCode}');
+ } else {
+ final list = (jsonDecode(r.body) as Map)['founds']
+ as List