diff --git a/api-data-store-server-java/.gitignore b/api-data-store-server-java/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..fe63103f29daac95f5989e4cfc75a0fb0cff693b
--- /dev/null
+++ b/api-data-store-server-java/.gitignore
@@ -0,0 +1,7 @@
+/build/
+/bin/
+/out/
+/.idea/
+/target/
+*.iml
+*.eml
\ No newline at end of file
diff --git a/api-data-store-server-java/build.gradle b/api-data-store-server-java/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..c770d54e3704c128ffeaa7f353a939fcde79c4fc
--- /dev/null
+++ b/api-data-store-server-java/build.gradle
@@ -0,0 +1,28 @@
+evaluationDependsOn(':lib-json')
+
+apply plugin: 'java'
+apply plugin: 'application'
+
+compileJava {
+    options.compilerArgs << '-parameters'
+}
+
+compileTestJava {
+    options.compilerArgs << '-parameters'
+}
+
+repositories {
+    ivy {
+        ivyPattern "https://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/[organisation]/[module]/[revision]/ivy.xml"
+        artifactPattern "https://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]"
+    }
+}
+
+dependencies {
+    compileOnly 'lombok:lombok:1.18.22'
+    annotationProcessor 'lombok:lombok:1.18.22'
+    implementation project(':lib-json'),
+            'lombok:lombok:1.18.22'
+    testImplementation 'junit:junit:4.10',
+            'hamcrest:hamcrest-core:1.3'
+}
diff --git a/api-data-store-server-java/gradle/wrapper/gradle-wrapper.jar b/api-data-store-server-java/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..5c2d1cf016b3885f6930543d57b744ea8c220a1a
Binary files /dev/null and b/api-data-store-server-java/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/api-data-store-server-java/gradle/wrapper/gradle-wrapper.properties b/api-data-store-server-java/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..7745c4ec463c7bb1c4ff6a5e58db239890345152
--- /dev/null
+++ b/api-data-store-server-java/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/gradle/distribution/7.4/gradle-7.4-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/api-data-store-server-java/gradlew b/api-data-store-server-java/gradlew
new file mode 100755
index 0000000000000000000000000000000000000000..a69d9cb6c20655813e44515156e7253a2a239138
--- /dev/null
+++ b/api-data-store-server-java/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/api-data-store-server-java/gradlew.bat b/api-data-store-server-java/gradlew.bat
new file mode 100644
index 0000000000000000000000000000000000000000..f127cfd49d4024c3e1e0d08ba56399221b4fb25d
--- /dev/null
+++ b/api-data-store-server-java/gradlew.bat
@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/api-data-store-server-java/settings.gradle b/api-data-store-server-java/settings.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..7cbef054c8bed632b3fc62c2d34bab78ff53a039
--- /dev/null
+++ b/api-data-store-server-java/settings.gradle
@@ -0,0 +1 @@
+includeFlat 'lib-json'
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/AuthenticationAPI.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/AuthenticationAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..898547b8a9c2eaed627cec94092cb543eeef8a14
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/AuthenticationAPI.java
@@ -0,0 +1,29 @@
+/*
+ *  Copyright ETH 2022 - 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.api;
+
+import lombok.NonNull;
+
+public interface AuthenticationAPI {
+    public String login(@NonNull String userId, @NonNull String password) throws Exception;
+
+    public Boolean isSessionValid() throws Exception;
+
+    public Boolean logout() throws Exception;
+}
+
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/api/OperationsAPI.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/OperationsAPI.java
similarity index 59%
rename from server-data-store/src/main/java/ch/ethz/sis/afsserver/api/OperationsAPI.java
rename to api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/OperationsAPI.java
index 36d28eb33b3fb59e6cdf99d3b7164dd03c0815aa..2afacb9b37e89a9906359c19572c2136bf55d45c 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/api/OperationsAPI.java
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/OperationsAPI.java
@@ -1,23 +1,25 @@
 /*
- * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services
+ *  Copyright ETH 2022 - 2023 Zürich, Scientific IT Services
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
  */
-package ch.ethz.sis.afsserver.api;
 
-import ch.ethz.sis.afs.api.dto.File;
+package ch.ethz.sis.afsapi.api;
+
 import java.util.List;
 
+import ch.ethz.sis.afsapi.dto.File;
 import lombok.NonNull;
 
 public interface OperationsAPI
@@ -46,4 +48,4 @@ public interface OperationsAPI
     Boolean move(@NonNull String sourceOwner, @NonNull String source, @NonNull String targetOwner,
             @NonNull String target) throws Exception;
 
-}
+}
\ No newline at end of file
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/PublicAPI.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/PublicAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..67ae9575bb884349960bc516267b60510d6b358b
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/PublicAPI.java
@@ -0,0 +1,21 @@
+/*
+ *  Copyright ETH 2022-2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.api;
+
+public interface PublicAPI extends OperationsAPI, AuthenticationAPI, TwoPhaseTransactionAPI {
+}
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/TwoPhaseTransactionAPI.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/TwoPhaseTransactionAPI.java
new file mode 100644
index 0000000000000000000000000000000000000000..58ebc6bf85e92146af8e735fadc589d4b1afc8d9
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/api/TwoPhaseTransactionAPI.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ch.ethz.sis.afsapi.api;
+
+import java.util.List;
+import java.util.UUID;
+
+public interface TwoPhaseTransactionAPI {
+
+    //
+    // 2PT API
+    //
+
+    void begin(UUID transactionId) throws Exception; // Starts or recovers an existing transaction
+
+    Boolean prepare() throws Exception; // Prepares the transaction serializing it to disk, if is already prepared ignores the command
+
+    void commit() throws Exception; // Commits the transaction, under any circumstances
+
+    void rollback() throws Exception; // Rollback a begin or prepared transaction
+
+    List<UUID> recover() throws Exception; // Returns the list of transactions on prepare state, used internally on crash recovery scenarios.
+
+}
\ No newline at end of file
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ApiResponse.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ApiResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..d133276f08ae5dded388be059e5404168eba5c15
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ApiResponse.java
@@ -0,0 +1,36 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.dto;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Value;
+
+@Value
+@Builder(toBuilder = true)
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
+public class ApiResponse {
+
+    String id;
+
+    Object result;
+
+    Object error;
+
+}
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ExceptionReason.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ExceptionReason.java
new file mode 100644
index 0000000000000000000000000000000000000000..a724e9523f4671b2cd651a5e93f551906f876286
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ExceptionReason.java
@@ -0,0 +1,32 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.dto;
+
+import lombok.Value;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Value
+public class ExceptionReason implements Serializable {
+    private Integer componentCode;
+    private Integer exceptionCode;
+    private String javaClassName;
+    private List<ExceptionType> types;
+    private String message;
+}
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ExceptionType.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ExceptionType.java
new file mode 100644
index 0000000000000000000000000000000000000000..a7585ac030c4655e1acce050addae78894210440
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/ExceptionType.java
@@ -0,0 +1,28 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.dto;
+
+public enum ExceptionType {
+    UnknownError,
+    UserUsageError,
+    AdminConfigError,
+    ClientDeveloperCodingError,
+    CoreDeveloperCodingError,
+    RecoverableSystemStateError,
+    IrrecoverableSystemStateError
+}
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/File.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/File.java
new file mode 100644
index 0000000000000000000000000000000000000000..05aa125caabe467ac02383b6223c7f42a3894611
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/dto/File.java
@@ -0,0 +1,37 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.dto;
+
+import lombok.*;
+
+import java.time.OffsetDateTime;
+
+@Value
+@Builder(toBuilder = true)
+@AllArgsConstructor(access = AccessLevel.PUBLIC)
+public class File {
+    private String path;
+    private String name;
+    private Boolean directory;
+    private Long size; // Size in bytes
+    private OffsetDateTime lastModifiedTime;
+    private OffsetDateTime creationTime;
+
+    @EqualsAndHashCode.Exclude
+    private OffsetDateTime lastAccessTime;
+}
\ No newline at end of file
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/BaseRuntimeExceptionTemplate.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/BaseRuntimeExceptionTemplate.java
new file mode 100644
index 0000000000000000000000000000000000000000..709b2848a87d14228256b2afbd2ce3f3ab039244
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/BaseRuntimeExceptionTemplate.java
@@ -0,0 +1,67 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.exception;
+
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.util.List;
+
+public abstract class BaseRuntimeExceptionTemplate<REASON extends Serializable, TYPE extends Enum> {
+    protected final Class clazz;
+    protected final int componentCode;
+    protected final int exceptionCode;
+    protected final String messageTemplate;
+    protected final List<TYPE> types;
+
+    public BaseRuntimeExceptionTemplate(int componentCode, Class clazz, List<TYPE> types, int exceptionCode, String messageTemplate) {
+        this.componentCode = componentCode;
+        this.clazz = clazz;
+        this.types = types;
+        this.exceptionCode = exceptionCode;
+        this.messageTemplate = messageTemplate;
+    }
+
+    public abstract REASON getReason(Object... args);
+
+    public ThrowableReason getThrowableReason(Object... args) {
+        return new ThrowableReason(getReason(args));
+    }
+
+    public RuntimeException getInstance(Object... args) {
+        RuntimeException exception;
+        try {
+            Constructor constructor = clazz.getConstructor(Throwable.class);
+            exception = (RuntimeException) constructor.newInstance(getThrowableReason(args));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return exception;
+    }
+
+    public Exception getCheckedInstance(Object... args) {
+        Exception exception;
+        try {
+            Constructor constructor = clazz.getConstructor(Throwable.class);
+            exception = (Exception) constructor.newInstance(getThrowableReason(args));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return exception;
+    }
+
+}
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/api/PublicAPI.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/ExceptionTemplateHolder.java
similarity index 77%
rename from server-data-store/src/main/java/ch/ethz/sis/afsserver/api/PublicAPI.java
rename to api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/ExceptionTemplateHolder.java
index c52ae8929c12d8ff95fc1dab4aa78454fbe749b3..02c5f2edcdb85df3a5d49ba7871a8a929d8e0959 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/api/PublicAPI.java
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/ExceptionTemplateHolder.java
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package ch.ethz.sis.afsserver.api;
+package ch.ethz.sis.afsapi.exception;
 
-import ch.ethz.sis.afs.api.TwoPhaseTransactionAPI;
-
-public interface PublicAPI extends OperationsAPI, AuthenticationAPI, TwoPhaseTransactionAPI {
+public interface ExceptionTemplateHolder
+{
+    public RuntimeException getInstance(Object... args) throws Exception;
 }
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/RuntimeExceptionTemplate.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/RuntimeExceptionTemplate.java
new file mode 100644
index 0000000000000000000000000000000000000000..398886ea0dd399e79554d8d01b8e8f396d49f2c0
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/RuntimeExceptionTemplate.java
@@ -0,0 +1,37 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsapi.exception;
+
+
+import ch.ethz.sis.afsapi.dto.ExceptionReason;
+import ch.ethz.sis.afsapi.dto.ExceptionType;
+
+import java.util.List;
+
+public class RuntimeExceptionTemplate extends BaseRuntimeExceptionTemplate<ExceptionReason, ExceptionType> {
+
+    public RuntimeExceptionTemplate(int componentCode, Class clazz, List<ExceptionType> serverExceptionTypes, int exceptionCode, String messageTemplate) {
+        super(componentCode, clazz, serverExceptionTypes, exceptionCode, messageTemplate);
+    }
+
+    @Override
+    public ExceptionReason getReason(Object... args) {
+        return new ExceptionReason(componentCode, exceptionCode, clazz.getName(), types, String.format(messageTemplate, args));
+    }
+}
+
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/api/AuthenticationAPI.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/ThrowableReason.java
similarity index 67%
rename from server-data-store/src/main/java/ch/ethz/sis/afsserver/api/AuthenticationAPI.java
rename to api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/ThrowableReason.java
index 16fd1a86692bcdc1a15ae466fb0a700742764174..87a9690baa0fa44a430517c8c57f8472d6dba5d3 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/api/AuthenticationAPI.java
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsapi/exception/ThrowableReason.java
@@ -13,14 +13,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package ch.ethz.sis.afsserver.api;
+package ch.ethz.sis.afsapi.exception;
 
-import lombok.NonNull;
+import java.io.Serializable;
 
-public interface AuthenticationAPI {
-    public String login(@NonNull String userId, @NonNull String password) throws Exception;
+public class ThrowableReason extends Throwable {
+    private Serializable reason;
 
-    public Boolean isSessionValid() throws Exception;
+    public ThrowableReason(Serializable reason) {
+        this.reason = reason;
+    }
 
-    public Boolean logout() throws Exception;
+    public Serializable getReason() {
+        return reason;
+    }
 }
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..63dc36dbcbea41f234b0348c8ba7348ff5bc47ea
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AfsClient.java
@@ -0,0 +1,255 @@
+package ch.ethz.sis.afsclient.client;
+
+import java.io.ByteArrayInputStream;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.*;
+import java.util.stream.Stream;
+
+import ch.ethz.sis.afsapi.api.PublicAPI;
+import ch.ethz.sis.afsapi.dto.ApiResponse;
+import ch.ethz.sis.afsapi.dto.File;
+import ch.ethz.sis.afsclient.client.exception.ClientExceptions;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
+import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper;
+import lombok.NonNull;
+
+public final class AfsClient implements PublicAPI
+{
+
+    private static final int DEFAULT_PACKAGE_SIZE_IN_BYTES = 1024;
+
+    private static final int DEFAULT_TIMEOUT_IN_MILLIS = 30000;
+
+    private final int maxReadSizeInBytes;
+
+    private final int timeout;
+
+    private String sessionToken;
+
+    private final URI serverUri;
+
+    private final JsonObjectMapper jsonObjectMapper;
+
+    public AfsClient(final URI serverUri)
+    {
+        this(serverUri, DEFAULT_PACKAGE_SIZE_IN_BYTES, DEFAULT_TIMEOUT_IN_MILLIS);
+    }
+
+    public AfsClient(final URI serverUri, final int maxReadSizeInBytes, final int timeout)
+    {
+        this.maxReadSizeInBytes = maxReadSizeInBytes;
+        this.timeout = timeout;
+        this.serverUri = serverUri;
+        this.jsonObjectMapper = new JacksonObjectMapper();
+    }
+
+    public URI getServerUri()
+    {
+        return serverUri;
+    }
+
+    public int getMaxReadSizeInBytes()
+    {
+        return maxReadSizeInBytes;
+    }
+
+    public String getSessionToken()
+    {
+        return sessionToken;
+    }
+
+    public void setSessionToken(final String sessionToken)
+    {
+        this.sessionToken = sessionToken;
+    }
+
+    private static String urlEncode(final String s)
+    {
+        return URLEncoder.encode(s, StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public @NonNull String login(@NonNull final String userId, @NonNull final String password)
+            throws Exception
+    {
+        String result = request("POST", "login", Map.of(),
+                (userId + ":" + password).getBytes());
+        setSessionToken(result);
+        return result;
+    }
+
+    @Override
+    public @NonNull Boolean isSessionValid() throws Exception
+    {
+        if (getSessionToken() == null)
+        {
+            throw new IllegalStateException("No session information detected!");
+        }
+        return request("GET", "isSessionValid", Map.of("sessionToken", getSessionToken()));
+    }
+
+    @Override
+    public @NonNull Boolean logout() throws Exception
+    {
+        if (getSessionToken() == null)
+        {
+            throw new IllegalStateException("No session information detected!");
+        }
+//      Boolean result = request("POST", "logout", Map.of(), getSessionToken().getBytes());
+        Boolean result = request("POST", "logout", Map.of("sessionToken", getSessionToken()));
+        setSessionToken(null);
+        return result;
+    }
+
+    @Override
+    public @NonNull List<File> list(@NonNull final String owner, @NonNull final String source,
+            @NonNull final Boolean recursively) throws Exception
+    {
+        return null;
+    }
+
+    @Override
+    public @NonNull byte[] read(@NonNull final String owner, @NonNull final String source,
+            @NonNull final Long offset, @NonNull final Integer limit) throws Exception
+    {
+        return new byte[0];
+    }
+
+    @Override
+    public @NonNull Boolean write(@NonNull final String owner, @NonNull final String source,
+            @NonNull final Long offset, final byte @NonNull [] data,
+            final byte @NonNull [] md5Hash) throws Exception
+    {
+        return null;
+    }
+
+    @Override
+    public @NonNull Boolean delete(@NonNull final String owner, @NonNull final String source)
+            throws Exception
+    {
+        return null;
+    }
+
+    @Override
+    public @NonNull Boolean copy(@NonNull final String sourceOwner, @NonNull final String source,
+            @NonNull final String targetOwner,
+            @NonNull final String target)
+            throws Exception
+    {
+        return null;
+    }
+
+    @Override
+    public @NonNull Boolean move(@NonNull final String sourceOwner, @NonNull final String source,
+            @NonNull final String targetOwner,
+            @NonNull final String target)
+            throws Exception
+    {
+        return null;
+    }
+
+    @Override
+    public void begin(final UUID transactionId) throws Exception
+    {
+
+    }
+
+    @Override
+    public Boolean prepare() throws Exception
+    {
+        return null;
+    }
+
+    @Override
+    public void commit() throws Exception
+    {
+
+    }
+
+    @Override
+    public void rollback() throws Exception
+    {
+
+    }
+
+    @Override
+    public List<UUID> recover() throws Exception
+    {
+        return null;
+    }
+
+    private <T> T request(@NonNull final String httpMethod, @NonNull final String apiMethod,
+            @NonNull final Map<String, String> parameters) throws Exception
+    {
+        return request(httpMethod, apiMethod, parameters, new byte[0]);
+    }
+
+    @SuppressWarnings({ "OptionalGetWithoutIsPresent", "unchecked" })
+    private <T> T request(@NonNull final String httpMethod, @NonNull final String apiMethod,
+            @NonNull final Map<String, String> parameters, final byte @NonNull [] body)
+            throws Exception
+    {
+        HttpClient.Builder clientBuilder = HttpClient.newBuilder()
+                .version(HttpClient.Version.HTTP_1_1)
+                .followRedirects(HttpClient.Redirect.NORMAL)
+                .connectTimeout(Duration.ofMillis(timeout));
+        Map<String, String> params = parameters;
+
+        HttpClient client = clientBuilder.build();
+
+        final String query = Stream.concat(
+                        Stream.of(new AbstractMap.SimpleImmutableEntry<>("method", apiMethod)),
+                        params.entrySet().stream())
+                .map(entry -> urlEncode(entry.getKey()) + "=" + urlEncode(entry.getValue()))
+                .reduce((s1, s2) -> s1 + "&" + s2).get();
+
+        final URI uri =
+                new URI(serverUri.getScheme(), null, serverUri.getHost(), serverUri.getPort(),
+                        serverUri.getPath(), query, null);
+
+        HttpRequest.Builder builder = HttpRequest.newBuilder()
+                .uri(uri)
+                .version(HttpClient.Version.HTTP_1_1)
+                .timeout(Duration.ofMillis(timeout))
+                .method(httpMethod, HttpRequest.BodyPublishers.ofByteArray(body));
+
+        final HttpRequest request = builder.build();
+
+        final HttpResponse<byte[]> httpResponse =
+                client.send(request, HttpResponse.BodyHandlers.ofByteArray());
+
+        final int statusCode = httpResponse.statusCode();
+        if (statusCode >= 200 && statusCode < 300)
+        {
+            final ApiResponse response =
+                    jsonObjectMapper.readValue(new ByteArrayInputStream(httpResponse.body()),
+                            ApiResponse.class);
+
+            if (response.getError() != null)
+            {
+                throw ClientExceptions.API_ERROR.getInstance(response.getError());
+            } else
+            {
+                return (T) response.getResult();
+            }
+        } else if (statusCode >= 400 && statusCode < 500)
+        {
+            throw ClientExceptions.CLIENT_ERROR.getInstance(statusCode);
+        } else if (statusCode >= 500 && statusCode < 600)
+        {
+            throw ClientExceptions.SERVER_ERROR.getInstance(statusCode);
+        } else
+        {
+            throw ClientExceptions.OTHER_ERROR.getInstance(statusCode);
+        }
+    }
+
+}
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/jackson/JSONObjectMapper.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AtomicFileSystemClientParameter.java
similarity index 50%
rename from lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/jackson/JSONObjectMapper.java
rename to api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AtomicFileSystemClientParameter.java
index 3f2bfcf067304029df9ba71ada88a7328345aed2..7c1ac8e11899d963039aeae94d47e179f7db0113 100644
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/jackson/JSONObjectMapper.java
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/AtomicFileSystemClientParameter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright ETH 2022 - 2023 Zürich, Scientific IT Services
+ * Copyright 2022 ETH Zürich, SIS
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,19 +13,24 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package ch.ethz.sis.shared.json.jackson;
 
-import java.io.InputStream;
-import java.nio.charset.Charset;
+package ch.ethz.sis.afsclient.client;
 
-public interface JSONObjectMapper {
+public enum AtomicFileSystemClientParameter {
+    //
+    // Parameters from the AFS Library
+    //
+    jsonObjectMapperClass,
 
-    Charset getCharset();
+    //
+    // Parameters for the HTTP server
+    //
+    httpServerPath,
 
-    void setCharset(Charset charset);
+    httpServerPort,
 
-    <T> T readValue(InputStream src, Class<T> valueType) throws Exception;
-
-    byte[] writeValue(Object value) throws Exception;
-
-}
+    //
+    // Parameters for the API server
+    //
+    maxReadSizeInBytes, // This is the chunk size used by the API, sizes between 1 and 6 megabytes are typical, anything bigger is unlikely to provide performance benefits because we are limited by the http package sizes
+}
\ No newline at end of file
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/exception/ClientExceptions.java b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/exception/ClientExceptions.java
new file mode 100644
index 0000000000000000000000000000000000000000..62ad2d74db0ffbbc43bcc3cd602b6ca512cf5cb0
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/afsclient/client/exception/ClientExceptions.java
@@ -0,0 +1,54 @@
+package ch.ethz.sis.afsclient.client.exception;
+
+import static ch.ethz.sis.afsapi.dto.ExceptionType.ClientDeveloperCodingError;
+import static ch.ethz.sis.afsapi.dto.ExceptionType.CoreDeveloperCodingError;
+import static ch.ethz.sis.afsapi.dto.ExceptionType.UnknownError;
+import static ch.ethz.sis.afsapi.dto.ExceptionType.UserUsageError;
+
+import java.util.List;
+
+import ch.ethz.sis.afsapi.dto.ExceptionReason;
+import ch.ethz.sis.afsapi.dto.ExceptionType;
+import ch.ethz.sis.afsapi.exception.RuntimeExceptionTemplate;
+import ch.ethz.sis.afsapi.exception.ExceptionTemplateHolder;
+
+public enum ClientExceptions implements ExceptionTemplateHolder {
+
+    // APIServer
+    UNKNOWN(RuntimeException.class, List.of(UnknownError), 50001,
+            "Unknown error of type %s, please contact support, this error comes with message: %s"),
+
+    CLIENT_ERROR(RuntimeException.class, List.of(ClientDeveloperCodingError), 50002,
+            "Client error HTTP response. Response code: %d"),
+
+    SERVER_ERROR(RuntimeException.class, List.of(CoreDeveloperCodingError), 50003,
+            "Server error HTTP response. Response code: %d"),
+
+    OTHER_ERROR(RuntimeException.class, List.of(UnknownError), 50004,
+            "Unexpected HTTP response. Response code: %d"),
+
+    API_ERROR(IllegalArgumentException.class, List.of(UserUsageError), 50005,
+            "API error. Error message: '%s'");
+
+    private final RuntimeExceptionTemplate template;
+
+    ClientExceptions(Class<?> clazz, List<ExceptionType> types, int code, String messageTemplate) {
+        this.template = new RuntimeExceptionTemplate(3, clazz, types, code, messageTemplate);
+    }
+
+    public RuntimeException getInstance(Object... args) {
+        return template.getInstance(args);
+    }
+
+    public Exception getCheckedInstance(Object... args) {
+        return template.getCheckedInstance(args);
+    }
+
+    public static void throwInstance(ClientExceptions exception, Object... args) {
+        throw exception.getInstance(args);
+    }
+
+    public ExceptionReason getCause(Object... args) {
+        return template.getReason(args);
+    }
+}
diff --git a/api-data-store-server-java/src/main/java/ch/ethz/sis/lombok.config b/api-data-store-server-java/src/main/java/ch/ethz/sis/lombok.config
new file mode 100644
index 0000000000000000000000000000000000000000..c8df289e28ed77a2f80207e256f1fde2bc72e7a5
--- /dev/null
+++ b/api-data-store-server-java/src/main/java/ch/ethz/sis/lombok.config
@@ -0,0 +1,4 @@
+lombok.nonNull.exceptionType = IllegalArgumentException
+lombok.addGeneratedAnnotation = false
+lombok.addJavaxGeneratedAnnotation = false
+lombok.anyConstructor.addConstructorProperties=true
\ No newline at end of file
diff --git a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a9ec352489faa11d83b5bfd371c1fbc6972a4cf7
--- /dev/null
+++ b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/AfsClientTest.java
@@ -0,0 +1,148 @@
+package ch.ethz.sis.afsclient.client;
+
+import static org.junit.Assert.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import java.net.URI;
+
+import org.junit.*;
+
+public class AfsClientTest
+{
+
+    private static DummyHttpServer httpServer;
+
+    private AfsClient afsClient;
+    private static final int HTTP_SERVER_PORT = 8085;
+    private static final String HTTP_SERVER_PATH = "/fileserver";
+
+    @Before
+    public void setUp() throws Exception {
+        httpServer = new DummyHttpServer(HTTP_SERVER_PORT, HTTP_SERVER_PATH);
+        httpServer.start();
+        afsClient = new AfsClient(
+                new URI("http", null, "localhost", HTTP_SERVER_PORT,
+                        HTTP_SERVER_PATH, null, null));
+    }
+
+    @After
+    public void tearDown() {
+        httpServer.stop();
+    }
+
+    @Test
+    public void login_methodIsPost() throws Exception
+    {
+        final String token = afsClient.login("test", "test");
+        assertNotNull(token);
+        assertEquals(token, afsClient.getSessionToken());
+        assertEquals("POST", httpServer.getHttpExchange().getRequestMethod());
+    }
+
+    @Test
+    public void isSessionValid_withoutLogin_throwsException() throws Exception
+    {
+        try
+        {
+           afsClient.isSessionValid();
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), containsString("No session information detected!"));
+        }
+    }
+
+    @Test
+    public void isSessionValid_afterLogin_methodIsGet() throws Exception
+    {
+        afsClient.login("test", "test");
+        httpServer.setNextResponse("{\"result\": true}");
+
+        Boolean result = afsClient.isSessionValid();
+
+        assertTrue(result);
+        assertEquals("GET", httpServer.getHttpExchange().getRequestMethod());
+    }
+
+    @Test
+    public void logout_withoutLogin_throwsException() throws Exception
+    {
+        try
+        {
+            afsClient.logout();
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), containsString("No session information detected!"));
+        }
+    }
+
+    @Test
+    public void logout_sessionTokenIsCleared() throws Exception
+    {
+        afsClient.login("test", "test");
+        assertNotNull(afsClient.getSessionToken());
+
+        httpServer.setNextResponse("{\"result\": true}");
+
+        Boolean result = afsClient.logout();
+        assertTrue(result);
+        assertNull(afsClient.getSessionToken());
+        assertEquals("POST", httpServer.getHttpExchange().getRequestMethod());
+    }
+
+    @Test
+    public void testList() throws Exception
+    {
+    }
+
+    @Test
+    public void testRead()throws Exception
+    {
+    }
+
+    @Test
+    public void testWrite()throws Exception
+    {
+    }
+
+    @Test
+    public void testDelete()throws Exception
+    {
+    }
+
+    @Test
+    public void testCopy()throws Exception
+    {
+    }
+
+    @Test
+    public void testMove()
+    {
+    }
+
+    @Test
+    public void testBegin()
+    {
+    }
+
+    @Test
+    public void testPrepare()
+    {
+    }
+
+    @Test
+    public void testCommit()
+    {
+    }
+
+    @Test
+    public void testRollback()
+    {
+    }
+
+    @Test
+    public void testRecover()
+    {
+    }
+
+}
\ No newline at end of file
diff --git a/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4d9337fce7294bc4a63ceaa945978785a6914bf
--- /dev/null
+++ b/api-data-store-server-java/src/test/java/ch/ethz/sis/afsclient/client/DummyHttpServer.java
@@ -0,0 +1,78 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsclient.client;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+public final class DummyHttpServer
+{
+    private HttpServer httpServer;
+
+    private final int httpServerPort; // 8085
+
+    private final String httpServerPath; // "/fileserver"
+
+    private static final String DEFAULT_RESPONSE = "{\"result\": \"success\"}";
+
+    private String nextResponse = DEFAULT_RESPONSE;
+    private HttpExchange httpExchange;
+
+    public DummyHttpServer(int httpServerPort, String httpServerPath) throws IOException
+    {
+        this.httpServerPort = httpServerPort;
+        this.httpServerPath = httpServerPath;
+        httpServer = HttpServer.create(new InetSocketAddress(httpServerPort), 0);
+        httpServer.createContext(httpServerPath, new HttpHandler()
+        {
+            public void handle(HttpExchange exchange) throws IOException
+            {
+                byte[] response = nextResponse.getBytes();
+                exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, response.length);
+                exchange.getResponseBody().write(response);
+                exchange.close();
+                httpExchange = exchange;
+            }
+        });
+    }
+
+    public void start()
+    {
+        httpServer.start();
+    }
+
+    public void stop()
+    {
+        httpServer.stop(0);
+    }
+
+    public void setNextResponse(String response)
+    {
+        this.nextResponse = response;
+    }
+
+    public HttpExchange getHttpExchange() {
+        return httpExchange;
+    }
+
+}
diff --git a/build/settings.gradle b/build/settings.gradle
index d790bf35e109db3f87a4755aed2bffbcc3734e9f..c0216a4822aedef3214cbab94c77c84d7583be67 100644
--- a/build/settings.gradle
+++ b/build/settings.gradle
@@ -1,4 +1,6 @@
-includeFlat 'lib-commonbase', 'lib-common', 'api-openbis-java', 'lib-openbis-common', 'lib-authentication', 'lib-dbmigration', 'server-application-server',
-        'server-original-data-store', 'server-screening', 'core-plugin-openbis', 'app-openbis-installer',
-        'lib-image-readers', 'test-ui-core', 'test-api-openbis-javascript', 'server-external-data-store', 'ui-admin',
-        'lib-microservice-server', 'lib-transactional-file-system', 'server-data-store', 'ui-eln-lims', 'api-openbis-javascript'
\ No newline at end of file
+includeFlat 'lib-commonbase', 'lib-common', 'api-openbis-java', 'lib-openbis-common', 'lib-authentication',
+        'lib-dbmigration', 'server-application-server', 'server-original-data-store', 'server-screening',
+        'core-plugin-openbis', 'app-openbis-installer', 'lib-image-readers', 'test-ui-core',
+        'test-api-openbis-javascript', 'server-external-data-store', 'ui-admin', 'lib-microservice-server',
+        'lib-transactional-file-system', 'server-data-store', 'ui-eln-lims', 'api-openbis-javascript',
+        'lib-json', 'api-data-store-server-java'
\ No newline at end of file
diff --git a/lib-json/.gitignore b/lib-json/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..fe63103f29daac95f5989e4cfc75a0fb0cff693b
--- /dev/null
+++ b/lib-json/.gitignore
@@ -0,0 +1,7 @@
+/build/
+/bin/
+/out/
+/.idea/
+/target/
+*.iml
+*.eml
\ No newline at end of file
diff --git a/lib-json/build.gradle b/lib-json/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..1c7c2813097ed41b51b0b485f5e37b6901b159c1
--- /dev/null
+++ b/lib-json/build.gradle
@@ -0,0 +1,12 @@
+apply from: '../build/javaproject.gradle'
+
+dependencies {
+    implementation 'fasterxml:jackson-annotations:2.9.10',
+        'fasterxml:jackson-core:2.9.10',
+        'fasterxml:jackson-databind:2.9.10.8',
+        'fasterxml:jackson-datatype-jsr310:2.9.10'
+
+    testImplementation 'testng:testng:6.8-CISD'
+
+    testRuntimeOnly 'hamcrest:hamcrest-core:1.3'
+}
diff --git a/lib-json/gradle/wrapper/gradle-wrapper.jar b/lib-json/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..5c2d1cf016b3885f6930543d57b744ea8c220a1a
Binary files /dev/null and b/lib-json/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/lib-json/gradle/wrapper/gradle-wrapper.properties b/lib-json/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..7745c4ec463c7bb1c4ff6a5e58db239890345152
--- /dev/null
+++ b/lib-json/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://sissource.ethz.ch/openbis/openbis-public/openbis-ivy/-/raw/main/gradle/distribution/7.4/gradle-7.4-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/lib-json/gradlew b/lib-json/gradlew
new file mode 100755
index 0000000000000000000000000000000000000000..a69d9cb6c20655813e44515156e7253a2a239138
--- /dev/null
+++ b/lib-json/gradlew
@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/lib-json/gradlew.bat b/lib-json/gradlew.bat
new file mode 100644
index 0000000000000000000000000000000000000000..f127cfd49d4024c3e1e0d08ba56399221b4fb25d
--- /dev/null
+++ b/lib-json/gradlew.bat
@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/lib-json/source/java/ch/ethz/sis/afsjson/JsonObjectMapper.java b/lib-json/source/java/ch/ethz/sis/afsjson/JsonObjectMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..a826a6f627a7efbafbc198d967223110f423bb7b
--- /dev/null
+++ b/lib-json/source/java/ch/ethz/sis/afsjson/JsonObjectMapper.java
@@ -0,0 +1,28 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsjson;
+
+import java.io.InputStream;
+
+public interface JsonObjectMapper
+{
+    <T> T readValue(InputStream src, Class<T> valueType) throws Exception;
+
+    byte[] writeValue(Object value) throws Exception;
+
+}
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/jackson/JacksonObjectMapper.java b/lib-json/source/java/ch/ethz/sis/afsjson/jackson/JacksonObjectMapper.java
similarity index 58%
rename from lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/jackson/JacksonObjectMapper.java
rename to lib-json/source/java/ch/ethz/sis/afsjson/jackson/JacksonObjectMapper.java
index 4ec1f9e1f75bb46437670c2e073bb6b0caf73eb7..45fff2e94374fbf8d58f0e2191b08f702f2e1ac1 100644
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/jackson/JacksonObjectMapper.java
+++ b/lib-json/source/java/ch/ethz/sis/afsjson/jackson/JacksonObjectMapper.java
@@ -1,29 +1,30 @@
 /*
- * Copyright ETH 2018 - 2023 Zürich, Scientific IT Services
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
  *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
  *
- *      http://www.apache.org/licenses/LICENSE-2.0
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
  */
-package ch.ethz.sis.shared.json.jackson;
 
-import ch.ethz.sis.shared.json.JSONObjectMapper;
-import com.fasterxml.jackson.core.type.TypeReference;
+package ch.ethz.sis.afsjson.jackson;
+
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 
 import java.io.InputStream;
 
-public class JacksonObjectMapper implements JSONObjectMapper
+public class JacksonObjectMapper implements JsonObjectMapper
 {
     //
     // Singleton
@@ -35,7 +36,7 @@ public class JacksonObjectMapper implements JSONObjectMapper
         jacksonObjectMapper = new JacksonObjectMapper();
     }
 
-    public static JSONObjectMapper getInstance()
+    public static JsonObjectMapper getInstance()
     {
         return jacksonObjectMapper;
     }
diff --git a/lib-json/source/java/ch/ethz/sis/afsjson/jackson/JsonObjectMapper.java b/lib-json/source/java/ch/ethz/sis/afsjson/jackson/JsonObjectMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0e74656097b7ec34f8181579f79c1cbc94b2336
--- /dev/null
+++ b/lib-json/source/java/ch/ethz/sis/afsjson/jackson/JsonObjectMapper.java
@@ -0,0 +1,33 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsjson.jackson;
+
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+public interface JsonObjectMapper {
+
+    Charset getCharset();
+
+    void setCharset(Charset charset);
+
+    <T> T readValue(InputStream src, Class<T> valueType) throws Exception;
+
+    byte[] writeValue(Object value) throws Exception;
+
+}
diff --git a/lib-transactional-file-system/build.gradle b/lib-transactional-file-system/build.gradle
index e9fc6a1dc7286f834adbd59fc556e30c349d190f..d64c65cbecff7d747737f62088d3990bea341472 100644
--- a/lib-transactional-file-system/build.gradle
+++ b/lib-transactional-file-system/build.gradle
@@ -14,7 +14,8 @@ repositories {
 
 dependencies {
     annotationProcessor 'lombok:lombok:1.18.22'
-    implementation 'lombok:lombok:1.18.22',
+    implementation  project(':lib-json'),
+                    'lombok:lombok:1.18.22',
                     'log4j:log4j-api:2.10.0',
                     'log4j:log4j-core:2.10.0',
                     'fasterxml:jackson-annotations:2.9.10',
diff --git a/lib-transactional-file-system/settings.gradle b/lib-transactional-file-system/settings.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..048a4c86d15e4deac0dbf45119c319fcf29fc615
--- /dev/null
+++ b/lib-transactional-file-system/settings.gradle
@@ -0,0 +1 @@
+includeFlat 'lib-json'
\ No newline at end of file
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java
index fbb4bea9fe957ab126e07658505617b27bf67a55..6b7780928c1f3499ba70e06ef74a982b3ef54859 100644
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java
+++ b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionConnection.java
@@ -22,7 +22,7 @@ import ch.ethz.sis.afs.exception.AFSExceptions;
 import ch.ethz.sis.afs.api.dto.File;
 import ch.ethz.sis.afs.manager.operation.*;
 import ch.ethz.sis.shared.io.IOUtils;
-import ch.ethz.sis.shared.json.JSONObjectMapper;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.afs.dto.Lock;
 import ch.ethz.sis.afs.dto.LockType;
 
@@ -47,7 +47,7 @@ public class TransactionConnection implements TransactionalFileSystem {
     }
 
     private LockManager<UUID, String> lockManager;
-    private JSONObjectMapper jsonObjectMapper;
+    private JsonObjectMapper jsonObjectMapper;
     private Transaction transaction;
     private State state;
     private String writeAheadLogRoot;
@@ -58,7 +58,7 @@ public class TransactionConnection implements TransactionalFileSystem {
      * Used only to create new transactions
      */
     TransactionConnection(LockManager<UUID, String> lockManager,
-                          JSONObjectMapper jsonObjectMapper,
+                          JsonObjectMapper jsonObjectMapper,
                           String writeAheadLogRoot,
                           String storageRoot,
                           RecoveredTransactions recoveredTransactions) {
@@ -72,7 +72,7 @@ public class TransactionConnection implements TransactionalFileSystem {
      * Can be used to recover a committed transactions after a crash
      */
     TransactionConnection(LockManager<UUID, String> lockManager,
-                          JSONObjectMapper jsonObjectMapper,
+                          JsonObjectMapper jsonObjectMapper,
                           Transaction transaction) {
         this.lockManager = lockManager;
         this.jsonObjectMapper = jsonObjectMapper;
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionManager.java b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionManager.java
index 47820c04c4987d0fb619df720d2b136e2703f287..a45509625ed372b12c3f74aea4d955241aece9f5 100644
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionManager.java
+++ b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/manager/TransactionManager.java
@@ -17,7 +17,7 @@ package ch.ethz.sis.afs.manager;
 
 import ch.ethz.sis.afs.api.dto.File;
 import ch.ethz.sis.shared.io.IOUtils;
-import ch.ethz.sis.shared.json.JSONObjectMapper;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.shared.log.LogManager;
 import ch.ethz.sis.shared.log.Logger;
 import ch.ethz.sis.afs.dto.Transaction;
@@ -37,12 +37,12 @@ public class TransactionManager {
     private static final Logger logger = LogManager.getLogger(TransactionManager.class);
 
     private LockManager<UUID, String> lockManager;
-    private JSONObjectMapper jsonObjectMapper;
+    private JsonObjectMapper jsonObjectMapper;
     private String writeAheadLogRoot;
     private String storageRoot;
     private RecoveredTransactions recoveredTransactions;
 
-    public TransactionManager(JSONObjectMapper jsonObjectMapper,
+    public TransactionManager(JsonObjectMapper jsonObjectMapper,
                               String writeAheadLogRoot,
                               String storageRoot) throws IOException {
         this.lockManager = new LockManager<>(new PathLockFinder());
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/startup/Main.java b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/startup/Main.java
index 3249bb23dba457bb9b9fed85fcd626b0d5cbe24b..d0f442d14e91bbd36c7e6d83a975d44b4b57da95 100644
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/startup/Main.java
+++ b/lib-transactional-file-system/src/main/java/ch/ethz/sis/afs/startup/Main.java
@@ -16,7 +16,7 @@
 package ch.ethz.sis.afs.startup;
 
 import ch.ethz.sis.afs.manager.TransactionManager;
-import ch.ethz.sis.shared.json.JSONObjectMapper;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.shared.log.LogFactory;
 import ch.ethz.sis.shared.log.LogFactoryFactory;
 import ch.ethz.sis.shared.log.LogManager;
@@ -48,7 +48,7 @@ public class Main
         LogManager.setLogFactory(logFactory);
 
         //
-        JSONObjectMapper jsonObjectMapper =
+        JsonObjectMapper jsonObjectMapper =
                 configuration.getSharableInstance(AtomicFileSystemParameter.jsonObjectMapperClass);
         String writeAheadLogRoot =
                 configuration.getStringProperty(AtomicFileSystemParameter.writeAheadLogRoot);
diff --git a/lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/JSONObjectMapper.java b/lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/JSONObjectMapper.java
deleted file mode 100644
index ab3befa85b9dce77ae5bf607c6c50b8cab88ec2c..0000000000000000000000000000000000000000
--- a/lib-transactional-file-system/src/main/java/ch/ethz/sis/shared/json/JSONObjectMapper.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright ETH 2018 - 2023 Zürich, Scientific IT Services
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ch.ethz.sis.shared.json;
-
-import java.io.InputStream;
-
-public interface JSONObjectMapper {
-    <T> T readValue(InputStream src, Class<T> valueType) throws Exception;
-
-    byte[] writeValue(Object value) throws Exception;
-
-}
diff --git a/lib-transactional-file-system/src/main/resources/afs-config.properties b/lib-transactional-file-system/src/main/resources/afs-config.properties
index 794e68d16b2d5bdbfd5c2b386c14b47262b87d73..29f2cb57ea70e543136d27009381023e34c26a66 100755
--- a/lib-transactional-file-system/src/main/resources/afs-config.properties
+++ b/lib-transactional-file-system/src/main/resources/afs-config.properties
@@ -1,5 +1,5 @@
 logFactoryClass=ch.ethz.sis.shared.log.log4j2.Log4J2LogFactory
-jsonObjectMapperClass=ch.ethz.sis.shared.json.jackson.JacksonObjectMapper
+jsonObjectMapperClass=ch.ethz.sis.afsjson.jackson.JacksonObjectMapper
 logConfigFile=afs-config-log4j2.xml
 # Where all the transactions information is written until the prepare step
 # For performance reasons should be on the save volume as the configured storage
diff --git a/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/AFSEnvironment.java b/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/AFSEnvironment.java
index 1d6a3fd97bb81a99ee13c4ec83b66b9f9740a2c5..1bded2ba24265f37ce5f0d8ea87aea373ec15cc5 100644
--- a/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/AFSEnvironment.java
+++ b/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/AFSEnvironment.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afs;
 
-import ch.ethz.sis.shared.json.jackson.JacksonObjectMapper;
+import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper;
 import ch.ethz.sis.shared.startup.Configuration;
 import ch.ethz.sis.shared.log.log4j2.Log4J2LogFactory;
 import ch.ethz.sis.afs.startup.AtomicFileSystemParameter;
diff --git a/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/manager/AbstractTransactionConnectionTest.java b/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/manager/AbstractTransactionConnectionTest.java
index c82068ceee204683e5ae31f7ba212a6b460ca8aa..c0bd1714f14b1c3d1501892a63c07c1f4677e041 100644
--- a/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/manager/AbstractTransactionConnectionTest.java
+++ b/lib-transactional-file-system/src/test/java/ch/ethz/sis/afs/manager/AbstractTransactionConnectionTest.java
@@ -17,7 +17,7 @@ package ch.ethz.sis.afs.manager;
 
 import ch.ethz.sis.afs.api.dto.File;
 import ch.ethz.sis.shared.io.IOUtils;
-import ch.ethz.sis.shared.json.JSONObjectMapper;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.afs.AFSEnvironment;
 import ch.ethz.sis.afs.AbstractTest;
 import ch.ethz.sis.afs.dto.Transaction;
@@ -38,7 +38,7 @@ import static ch.ethz.sis.shared.io.IOUtils.setFilePermissions;
 
 public abstract class AbstractTransactionConnectionTest extends AbstractTest {
 
-    private JSONObjectMapper jsonObjectMapper;
+    private JsonObjectMapper jsonObjectMapper;
     private LockManager<UUID, String> lockManager;
     private TransactionConnection transaction;
 
diff --git a/server-data-store/build.gradle b/server-data-store/build.gradle
index 6d5b75d246b93b5d04a423a596fb3e579be8afb9..b468482a87654872625e93d67492554f679e1fef 100644
--- a/server-data-store/build.gradle
+++ b/server-data-store/build.gradle
@@ -1,3 +1,7 @@
+evaluationDependsOn(':lib-json')
+evaluationDependsOn(':api-data-store-server-java')
+evaluationDependsOn(':lib-transactional-file-system')
+
 apply plugin: 'java'
 apply plugin: 'application'
 
@@ -15,13 +19,15 @@ repositories {
 dependencies {
     annotationProcessor 'lombok:lombok:1.18.22'
     implementation project(':lib-transactional-file-system'),
+            project(':api-data-store-server-java'),
+            project(':lib-json'),
             'lombok:lombok:1.18.22',
             'io.netty:netty-all:4.1.68.Final',
             'log4j:log4j-api:2.10.0',
             'log4j:log4j-core:2.10.0',
             'openbis:openbis-v3-api-batteries-included:20.10.5';
-    testImplementation 'junit:junit:4.10'
-    testRuntimeOnly 'hamcrest:hamcrest-core:1.3'
+    testImplementation 'junit:junit:4.10',
+            'hamcrest:hamcrest-core:1.3'
 }
 
 task AFSServerDevelopmentEnvironmentStart(type: JavaExec) {
diff --git a/server-data-store/settings.gradle b/server-data-store/settings.gradle
index b1fcbd28024378b0ab6157e63697c10cfdf01989..47d57e7460445ba72b7781ac397d5068a34fff2c 100644
--- a/server-data-store/settings.gradle
+++ b/server-data-store/settings.gradle
@@ -1 +1 @@
-includeFlat 'lib-transactional-file-system'
+includeFlat 'lib-transactional-file-system', 'api-data-store-server-java', 'lib-json'
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java
index b9efb2ba185fd2e3dedbfe9d0b639cb0cb9b3241..50099d69e6814d9989fbe65405320b8403ef096c 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandler.java
@@ -26,75 +26,96 @@ import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.codec.http.*;
 
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
 import java.util.Set;
 
 import static io.netty.handler.codec.http.HttpMethod.*;
 
-public class NettyHttpHandler extends ChannelInboundHandlerAdapter {
+public class NettyHttpHandler extends ChannelInboundHandlerAdapter
+{
 
     private static final Logger logger = LogManager.getLogger(NettyHttpServer.class);
+
     private static final byte[] NOT_FOUND = "404 NOT FOUND".getBytes();
+
     private static final ByteBuf NOT_FOUND_BUFFER = Unpooled.wrappedBuffer(NOT_FOUND);
+
     private static final Set<HttpMethod> allowedMethods = Set.of(GET, POST, PUT, DELETE);
 
     private final String uri;
+
     private final HttpServerHandler httpServerHandler;
 
-    public NettyHttpHandler(String uri, HttpServerHandler httpServerHandler) {
+    public NettyHttpHandler(String uri, HttpServerHandler httpServerHandler)
+    {
         this.uri = uri;
         this.httpServerHandler = httpServerHandler;
     }
 
     @Override
-    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
-        if (msg instanceof FullHttpRequest) {
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
+    {
+        if (msg instanceof FullHttpRequest)
+        {
             final FullHttpRequest request = (FullHttpRequest) msg;
             QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.uri(), true);
             if (queryStringDecoder.path().equals(uri) &&
-                    allowedMethods.contains(request.method())) {
+                    allowedMethods.contains(request.method()))
+            {
                 FullHttpResponse response = null;
                 ByteBuf content = request.content();
-                try {
-                    byte[] contentAsArray = (content.hasArray())?content.array():null;
-                    HttpResponse apiResponse = httpServerHandler.process(request.method(), queryStringDecoder.parameters(), contentAsArray);
-                    HttpResponseStatus status = (!apiResponse.isError())?HttpResponseStatus.OK:HttpResponseStatus.BAD_REQUEST;
+                try
+                {
+                    byte[] array = new byte[content.readableBytes()];
+                    content.readBytes(array);
+                    HttpResponse apiResponse = httpServerHandler.process(request.method(),
+                            queryStringDecoder.parameters(), array);
+                    HttpResponseStatus status = (!apiResponse.isError()) ?
+                            HttpResponseStatus.OK :
+                            HttpResponseStatus.BAD_REQUEST;
                     response = getHttpResponse(
                             status,
                             apiResponse.getContentType(),
                             Unpooled.wrappedBuffer(apiResponse.getBody()),
                             apiResponse.getBody().length);
                     ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
-                } finally {
+                } finally
+                {
                     content.release();
                 }
-            } else {
+            } else
+            {
                 FullHttpResponse response = getHttpResponse(
-                            HttpResponseStatus.NOT_FOUND,
-                            "text/plain",
-                            NOT_FOUND_BUFFER,
-                            NOT_FOUND.length);
+                        HttpResponseStatus.NOT_FOUND,
+                        "text/plain",
+                        NOT_FOUND_BUFFER,
+                        NOT_FOUND.length);
                 ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
             }
-        } else {
+        } else
+        {
             super.channelRead(ctx, msg);
         }
     }
 
     @Override
-    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception
+    {
         ctx.flush();
     }
 
     @Override
-    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
+    {
         logger.catching(cause);
         byte[] causeBytes = cause.getMessage().getBytes();
         FullHttpResponse response = getHttpResponse(
-                    HttpResponseStatus.INTERNAL_SERVER_ERROR,
-                    "text/plain",
-                    Unpooled.wrappedBuffer(causeBytes),
-                    causeBytes.length
-                    );
+                HttpResponseStatus.INTERNAL_SERVER_ERROR,
+                "text/plain",
+                Unpooled.wrappedBuffer(causeBytes),
+                causeBytes.length
+        );
         ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
     }
 
@@ -102,7 +123,8 @@ public class NettyHttpHandler extends ChannelInboundHandlerAdapter {
             HttpResponseStatus status,
             String contentType,
             ByteBuf content,
-            int contentLength) {
+            int contentLength)
+    {
         FullHttpResponse response = new DefaultFullHttpResponse(
                 HttpVersion.HTTP_1_1,
                 status,
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java
index b96132285bfa51c82b2207b5bd0dc01713960c8e..dbfa4dc78b0c0e82dc6094df920116f6afc0c58b 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpServer.java
@@ -31,77 +31,101 @@ import io.netty.handler.codec.http.HttpObjectAggregator;
 import io.netty.handler.codec.http.HttpServerCodec;
 import io.netty.util.concurrent.Future;
 
-public class NettyHttpServer implements HttpServer {
+public class NettyHttpServer implements HttpServer
+{
 
     private static final Logger logger = LogManager.getLogger(NettyHttpServer.class);
 
     private final EventLoopGroup masterGroup;
+
     private final EventLoopGroup slaveGroup;
 
     private ChannelFuture channel;
 
-    public NettyHttpServer() {
+    public NettyHttpServer()
+    {
         masterGroup = new NioEventLoopGroup();
         slaveGroup = new NioEventLoopGroup();
     }
 
-    public void start(int port, int maxContentLength, String uri, HttpServerHandler httpServerHandler) {
+    public void start(int port, int maxContentLength, String uri,
+            HttpServerHandler httpServerHandler)
+    {
         Integer maxQueueLengthForIncomingConnections = 128;
 
-        Runtime.getRuntime().addShutdownHook(new Thread() {
+        Runtime.getRuntime().addShutdownHook(new Thread()
+        {
             @Override
-            public void run() {
+            public void run()
+            {
                 shutdown(true);
             }
         });
 
-        try {
+        try
+        {
             final ServerBootstrap bootstrap = new ServerBootstrap()
                     .group(masterGroup, slaveGroup)
                     .channel(NioServerSocketChannel.class)
-                    .childHandler(new ChannelInitializer<SocketChannel>() {
+                    .childHandler(new ChannelInitializer<SocketChannel>()
+                    {
                         @Override
-                        public void initChannel(final SocketChannel ch) throws Exception {
+                        public void initChannel(final SocketChannel ch) throws Exception
+                        {
                             ch.pipeline().addLast("codec", new HttpServerCodec());
-                            ch.pipeline().addLast("aggregator", new HttpObjectAggregator(maxContentLength));
-                            ch.pipeline().addLast("request", new NettyHttpHandler(uri, httpServerHandler));
+                            ch.pipeline().addLast("aggregator",
+                                    new HttpObjectAggregator(maxContentLength));
+                            ch.pipeline().addLast("request",
+                                    new NettyHttpHandler(uri, httpServerHandler));
                         }
                     })
                     .option(ChannelOption.SO_BACKLOG, maxQueueLengthForIncomingConnections)
                     .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                     .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
             channel = bootstrap.bind(port).sync();
-        } catch (final Exception ex) {
+        } catch (final Exception ex)
+        {
             logger.catching(ex);
         }
     }
 
-    public void shutdown(boolean gracefully) {
-        try {
+    public void shutdown(boolean gracefully)
+    {
+        try
+        {
             channel.channel().close();
-        } catch (Exception ex) {
+        } catch (Exception ex)
+        {
             logger.catching(ex);
         }
 
-        try {
-            if (gracefully) {
+        try
+        {
+            if (gracefully)
+            {
                 Future slaveShutdown = slaveGroup.shutdownGracefully();
                 slaveShutdown.await();
-            } else {
+            } else
+            {
                 slaveGroup.shutdown();
             }
-        } catch (Exception ex) {
+        } catch (Exception ex)
+        {
             logger.catching(ex);
         }
 
-        try {
-            if (gracefully) {
+        try
+        {
+            if (gracefully)
+            {
                 Future masterShutdown = masterGroup.shutdownGracefully();
                 masterShutdown.await();
-            } else {
+            } else
+            {
                 masterGroup.shutdown();
             }
-        } catch (Exception ex) {
+        } catch (Exception ex)
+        {
             logger.catching(ex);
         }
     }
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Server.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Server.java
index 8582537ad240b3a1cdfbd8cbde60f5c9cf8ecedf..21f9c7eac09c6284bf8f445475732cafa5eee614 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Server.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Server.java
@@ -20,7 +20,7 @@ import ch.ethz.sis.afsserver.server.impl.ApiServerAdapter;
 import ch.ethz.sis.afsserver.server.observer.APIServerObserver;
 import ch.ethz.sis.afsserver.server.observer.ServerObserver;
 import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter;
-import ch.ethz.sis.shared.json.jackson.JacksonObjectMapper;
+import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper;
 import ch.ethz.sis.shared.log.LogFactory;
 import ch.ethz.sis.shared.log.LogFactoryFactory;
 import ch.ethz.sis.shared.log.LogManager;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Worker.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Worker.java
index cb416565e05369020205a93ef236972c780c3b06..82ea2a874a0e25fb41b76640b08f8e730f20853c 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Worker.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/Worker.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afsserver.server;
 
-import ch.ethz.sis.afsserver.api.PublicAPI;
+import ch.ethz.sis.afsapi.api.PublicAPI;
 import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor;
 
 public interface Worker<CONNECTION> extends PublicAPI {
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
index c9ee0aebd8e934c62ad0bade6f3c6d5ba0727276..3a8f9991c53b9ca39bcf5eaad8b20dc006e8e594 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapter.java
@@ -15,13 +15,15 @@
  */
 package ch.ethz.sis.afsserver.server.impl;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import ch.ethz.sis.afsserver.exception.HTTPExceptions;
 import ch.ethz.sis.afsserver.http.*;
 import ch.ethz.sis.afsserver.server.*;
 import ch.ethz.sis.afsserver.server.performance.Event;
 import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor;
 import ch.ethz.sis.shared.io.IOUtils;
-import ch.ethz.sis.shared.json.JSONObjectMapper;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.shared.log.LogManager;
 import ch.ethz.sis.shared.log.Logger;
 import io.netty.handler.codec.http.HttpMethod;
@@ -31,50 +33,56 @@ import java.util.*;
 /*
  * This class is supposed to be called by a TCP or HTTP transport class
  */
-public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler {
+public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler
+{
 
     private static final Logger logger = LogManager.getLogger(ApiServerAdapter.class);
 
     private final APIServer<CONNECTION, Request, Response, API> server;
-    private final JSONObjectMapper jsonObjectMapper;
-    private final ApiResponseBuilder apiResponseBuilder;
 
+    private final JsonObjectMapper jsonObjectMapper;
+
+    private final ApiResponseBuilder apiResponseBuilder;
 
     public ApiServerAdapter(
             APIServer<CONNECTION, Request, Response, API> server,
-            JSONObjectMapper jsonObjectMapper) {
+            JsonObjectMapper jsonObjectMapper)
+    {
         this.server = server;
         this.jsonObjectMapper = jsonObjectMapper;
         this.apiResponseBuilder = new ApiResponseBuilder();
     }
 
-    public static HttpMethod getHttpMethod(String apiMethod) {
-        HttpMethod httpMethod = null;
-        switch (apiMethod){
-            case "delete":
-                httpMethod = HttpMethod.DELETE;
-                break;
-            case "write":
-                httpMethod = HttpMethod.PUT;
-                break;
+    public static HttpMethod getHttpMethod(String apiMethod)
+    {
+        switch (apiMethod)
+        {
             case "list":
             case "read":
             case "isSessionValid":
-                httpMethod = HttpMethod.GET;
-                break;
-            default:
-                httpMethod = HttpMethod.POST;
+                return HttpMethod.GET; // all parameters from GET methods come on the query string
+            case "write":
+            case "move":
+            case "login":
+            case "logout":
+                return HttpMethod.POST; // all parameters from POST methods come on the body
+            case "delete":
+                return HttpMethod.DELETE; // all parameters from DELETE methods come on the body
         }
-        return httpMethod;
+        throw new UnsupportedOperationException("This line SHOULD NOT be unreachable!");
     }
 
-    public boolean isValidMethod(HttpMethod givenMethod, String apiMethod) {
+    public boolean isValidMethod(HttpMethod givenMethod, String apiMethod)
+    {
         HttpMethod correctMethod = getHttpMethod(apiMethod);
         return correctMethod == givenMethod;
     }
 
-    public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> uriParameters, byte[] requestBody) {
-        try {
+    public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> uriParameters,
+            byte[] requestBody)
+    {
+        try
+        {
             logger.traceAccess(null);
             PerformanceAuditor performanceAuditor = new PerformanceAuditor();
 
@@ -83,22 +91,31 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler {
             String interactiveSessionKey = null;
             String transactionManagerKey = null;
             Map<String, Object> methodParameters = new HashMap<>();
-            for (Map.Entry<String, List<String>> entry:uriParameters.entrySet()) {
+            for (Map.Entry<String, List<String>> entry : uriParameters.entrySet())
+            {
                 String value = null;
-                if (entry.getValue() != null) {
-                    if (entry.getValue().size() == 1) {
+                if (entry.getValue() != null)
+                {
+                    if (entry.getValue().size() == 1)
+                    {
                         value = entry.getValue().get(0);
-                    } else if (entry.getValue().size() > 1) {
-                        return getHTTPResponse(new ApiResponse("1", null, HTTPExceptions.INVALID_PARAMETERS.getCause()));
+                    } else if (entry.getValue().size() > 1)
+                    {
+                        return getHTTPResponse(new ApiResponse("1", null,
+                                HTTPExceptions.INVALID_PARAMETERS.getCause()));
                     }
                 }
 
-                try {
-                    switch (entry.getKey()) {
+                try
+                {
+                    switch (entry.getKey())
+                    {
                         case "method":
                             method = value;
-                            if (!isValidMethod(httpMethod, method)) {
-                                return getHTTPResponse(new ApiResponse("1", null, HTTPExceptions.INVALID_HTTP_METHOD.getCause()));
+                            if (!isValidMethod(httpMethod, method))
+                            {
+                                return getHTTPResponse(new ApiResponse("1", null,
+                                        HTTPExceptions.INVALID_HTTP_METHOD.getCause()));
                             }
                             break;
                         case "sessionToken":
@@ -129,54 +146,80 @@ public class ApiServerAdapter<CONNECTION, API> implements HttpServerHandler {
                             methodParameters.put(entry.getKey(), value);
                             break;
                     }
-                } catch (Exception e) {
+                } catch (Exception e)
+                {
                     logger.catching(e);
-                    return getHTTPResponse(new ApiResponse("1", null, HTTPExceptions.INVALID_PARAMETERS.getCause(e.getClass().getSimpleName(), e.getMessage())));
+                    return getHTTPResponse(new ApiResponse("1", null,
+                            HTTPExceptions.INVALID_PARAMETERS.getCause(e.getClass().getSimpleName(),
+                                    e.getMessage())));
                 }
             }
 
-            if (method.equals("write")) {
-                methodParameters.put("data", requestBody);
+            switch (method) {
+                case "write":
+                    methodParameters.put("data", requestBody);
+                    break;
+                case "login":
+                    // userId : password
+                    String[] credentials = new String(requestBody, UTF_8).split(":");
+                    methodParameters.put("userId", credentials[0]);
+                    methodParameters.put("password", credentials[1]);
+                    break;
             }
 
-            ApiRequest apiRequest = new ApiRequest("1", method, methodParameters, sessionToken, interactiveSessionKey, transactionManagerKey);
-            Response response = server.processOperation(apiRequest, apiResponseBuilder, performanceAuditor);
+
+            ApiRequest apiRequest = new ApiRequest("1", method, methodParameters, sessionToken,
+                    interactiveSessionKey, transactionManagerKey);
+            Response response =
+                    server.processOperation(apiRequest, apiResponseBuilder, performanceAuditor);
             HttpResponse httpResponse = getHTTPResponse(response);
             performanceAuditor.audit(Event.WriteResponse);
             logger.traceExit(performanceAuditor);
             logger.traceExit(httpResponse);
             return httpResponse;
-        } catch (APIServerException e) {
+        } catch (APIServerException e)
+        {
             logger.catching(e);
-            switch (e.getType()) {
+            switch (e.getType())
+            {
                 case MethodNotFound:
                 case IncorrectParameters:
                 case InternalError:
-                    try {
+                    try
+                    {
                         return getHTTPResponse(new ApiResponse("1", null, e.getData()));
-                    } catch (Exception ex) {
+                    } catch (Exception ex)
+                    {
                         logger.catching(ex);
                     }
             }
-        } catch (Exception e) {
+        } catch (Exception e)
+        {
             logger.catching(e);
-            try {
-                return getHTTPResponse(new ApiResponse("1", null, HTTPExceptions.UNKNOWN.getCause(e.getClass().getSimpleName(), e.getMessage())));
-            } catch (Exception ex) {
+            try
+            {
+                return getHTTPResponse(new ApiResponse("1", null,
+                        HTTPExceptions.UNKNOWN.getCause(e.getClass().getSimpleName(),
+                                e.getMessage())));
+            } catch (Exception ex)
+            {
                 logger.catching(ex);
             }
         }
         return null; // This should never happen, it would mean an error writing the Unknown error happened.
     }
 
-    private HttpResponse getHTTPResponse(Response response) throws Exception {
+    private HttpResponse getHTTPResponse(Response response) throws Exception
+    {
         boolean error = response.getError() != null;
         String contentType = null;
         byte[] body = null;
-        if (response.getResult() instanceof byte[]) {
+        if (response.getResult() instanceof byte[])
+        {
             contentType = HttpResponse.CONTENT_TYPE_BINARY_DATA;
             body = (byte[]) response.getResult();
-        } else {
+        } else
+        {
             contentType = HttpResponse.CONTENT_TYPE_JSON;
             body = jsonObjectMapper.writeValue(response);
         }
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/startup/Main.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/startup/Main.java
index cde81d249589c1475cf809a2b21772b3cd5a8b4a..aac4b1ffffa4c9e27b6b5a866dbb4a65c8e66ebf 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/startup/Main.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/startup/Main.java
@@ -34,7 +34,7 @@ public class Main
     {
         System.out.println("Current Working Directory: " + (new File("")).getCanonicalPath());
         Configuration configuration = new Configuration(getParameterClasses(),
-                "../server-data-store/src/main/resources/afs-server-config.properties");
+                "../server-data-store/src/main/resources/server-data-store-config.properties");
         DummyServerObserver dummyServerObserver = new DummyServerObserver();
         Server server = new Server(configuration, dummyServerObserver, dummyServerObserver);
         Thread.currentThread().join();
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/AbstractProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/AbstractProxy.java
index 4ded4e936b36f5ae3809a2bdd50b91f28e74652c..6e075316ec5f3fcb499e851866674cdcf3832ed0 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/AbstractProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/AbstractProxy.java
@@ -16,7 +16,7 @@
 package ch.ethz.sis.afsserver.worker;
 
 import ch.ethz.sis.afs.api.TransactionalFileSystem;
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afsserver.server.Worker;
 import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor;
 import lombok.NonNull;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuditorProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuditorProxy.java
index 28bf7dcb21c48fce3afa3bfa4d3cd1b155dd6d24..bdcf0eb0e9d15b8729b398443df6cb8ca57a2272 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuditorProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuditorProxy.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afsserver.worker.proxy;
 
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afsserver.worker.AbstractProxy;
 import ch.ethz.sis.afsserver.server.performance.Event;
 import lombok.NonNull;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthenticationProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthenticationProxy.java
index b530233e2a787857a0ebec82b9a7f8551c26f8fe..5a3b033a5cfafec540c133245e4360e1be6cd96e 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthenticationProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthenticationProxy.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afsserver.worker.proxy;
 
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afsserver.exception.FSExceptions;
 import ch.ethz.sis.afsserver.worker.AbstractProxy;
 import ch.ethz.sis.afsserver.worker.providers.AuthenticationInfoProvider;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthorizationProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthorizationProxy.java
index 9670fc0ca85140751f1bb85525907826a169cd0d..88ba76d7e8960386671813b15952506741f15c2e 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthorizationProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/AuthorizationProxy.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afsserver.worker.proxy;
 
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afs.dto.operation.OperationName;
 import ch.ethz.sis.afsserver.exception.FSExceptions;
 import ch.ethz.sis.afsserver.worker.AbstractProxy;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
index 38a7eed7b82202b5c14ad14b4c87d3ff0ba5e186..ab041bb13c81c85b315347e96461eae5d6235203 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ExecutorProxy.java
@@ -15,12 +15,13 @@
  */
 package ch.ethz.sis.afsserver.worker.proxy;
 
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afsserver.worker.AbstractProxy;
 import ch.ethz.sis.shared.io.IOUtils;
 
 import java.util.List;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 public class ExecutorProxy extends AbstractProxy {
 
@@ -68,7 +69,15 @@ public class ExecutorProxy extends AbstractProxy {
 
     @Override
     public List<File> list(String owner, String source, Boolean recursively) throws Exception {
-        return workerContext.getConnection().list(getPath(owner, source), recursively);
+        return workerContext.getConnection().list(getPath(owner, source), recursively)
+                .stream()
+                .map(this::convertToFile)
+                .collect(Collectors.toList());
+    }
+
+    private File convertToFile(ch.ethz.sis.afs.api.dto.File file) {
+        return new File(file.getPath(), file.getName(), file.getDirectory(), file.getSize(),
+                file.getLastModifiedTime(), file.getCreationTime(), file.getLastAccessTime());
     }
 
     @Override
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/LogProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/LogProxy.java
index 38ccfc8374e262ead411d2e084eb956cd674cfc3..0268a104ac162c49d609ba30b901fbc675c3f684 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/LogProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/LogProxy.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afsserver.worker.proxy;
 
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afsserver.worker.AbstractProxy;
 import ch.ethz.sis.shared.log.LogManager;
 import ch.ethz.sis.shared.log.Logger;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ValidationProxy.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ValidationProxy.java
index 21a6b8bc7d339636e630b3eb1970ceedeaaf133b..10fa6b7ca3da3388515d50b2f932d1ec1d011551 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ValidationProxy.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/worker/proxy/ValidationProxy.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afsserver.worker.proxy;
 
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afsserver.exception.FSExceptions;
 import ch.ethz.sis.afsserver.worker.AbstractProxy;
 
diff --git a/server-data-store/src/main/resources/afs-server-config-log4j2.xml b/server-data-store/src/main/resources/log4j2.xml
similarity index 100%
rename from server-data-store/src/main/resources/afs-server-config-log4j2.xml
rename to server-data-store/src/main/resources/log4j2.xml
diff --git a/server-data-store/src/main/resources/afs-server-config.properties b/server-data-store/src/main/resources/server-data-store-config.properties
similarity index 88%
rename from server-data-store/src/main/resources/afs-server-config.properties
rename to server-data-store/src/main/resources/server-data-store-config.properties
index 138f6513d66cd2999e4b88b4220119acbd332bc4..461e66d29a04fbb952632e9d80c72fa9c8adc0a3 100755
--- a/server-data-store/src/main/resources/afs-server-config.properties
+++ b/server-data-store/src/main/resources/server-data-store-config.properties
@@ -1,7 +1,7 @@
 logFactoryClass=ch.ethz.sis.shared.log.log4j2.Log4J2LogFactory
 logConfigFile=afs-config-log4j2.xml
 
-jsonObjectMapperClass=ch.ethz.sis.shared.json.jackson.JacksonObjectMapper
+jsonObjectMapperClass=ch.ethz.sis.afsjson.jackson.JacksonObjectMapper
 # Where all the transactions information is written until the prepare step
 # For performance reasons should be on the save volume as the configured storage
 writeAheadLogRoot=./target/tests/transactions
@@ -18,7 +18,7 @@ authorizationInfoProviderClass=ch.ethz.sis.afsserver.worker.providers.impl.Dummy
 poolSize=50
 connectionFactoryClass=ch.ethz.sis.afsserver.worker.ConnectionFactory
 workerFactoryClass=ch.ethz.sis.afsserver.worker.WorkerFactory
-publicApiInterface=ch.ethz.sis.afsserver.api.PublicAPI
+publicApiInterface=ch.ethz.sis.afsapi.api.PublicAPI
 apiServerInteractiveSessionKey=1234
 apiServerTransactionManagerKey=5678
 apiServerWorkerTimeout=30000
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9065b602411801a27fb50c38a61efa807e413d2
--- /dev/null
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ApiClientTest.java
@@ -0,0 +1,131 @@
+/*
+ *  Copyright ETH 2023 Zürich, Scientific IT Services
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package ch.ethz.sis.afsserver;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.*;
+
+import java.net.URI;
+import java.util.List;
+
+import org.junit.*;
+
+import ch.ethz.sis.afs.manager.TransactionConnection;
+import ch.ethz.sis.afsclient.client.AfsClient;
+import ch.ethz.sis.afsserver.server.Server;
+import ch.ethz.sis.afsserver.server.observer.impl.DummyServerObserver;
+import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter;
+import ch.ethz.sis.shared.startup.Configuration;
+
+public final class ApiClientTest
+{
+    private static Server<TransactionConnection, ?> afsServer;
+
+    private static AfsClient afsClient;
+
+    private static int httpServerPort;
+
+    private static String httpServerPath;
+
+    @BeforeClass
+    public static void classSetUp() throws Exception
+    {
+        final Configuration configuration =
+                new Configuration(List.of(AtomicFileSystemServerParameter.class),
+                        "src/test/resources/test-server-config.properties");
+        final DummyServerObserver dummyServerObserver = new DummyServerObserver();
+        afsServer = new Server<>(configuration, dummyServerObserver, dummyServerObserver);
+        httpServerPort =
+                configuration.getIntegerProperty(AtomicFileSystemServerParameter.httpServerPort);
+        httpServerPath =
+                configuration.getStringProperty(AtomicFileSystemServerParameter.httpServerUri);
+    }
+
+    @Before
+    public void setUp() throws Exception
+    {
+        afsClient = new AfsClient(
+                new URI("http", null, "localhost", httpServerPort,
+                        httpServerPath, null, null));
+    }
+
+    private String login() throws Exception
+    {
+        return afsClient.login("test", "test");
+    }
+
+    @AfterClass
+    public static void classTearDown() throws Exception
+    {
+        afsServer.shutdown(true);
+    }
+
+    @Test
+    public void login_sessionTokenIsNotNull() throws Exception
+    {
+        final String token = login();
+        assertNotNull(token);
+    }
+
+    @Test
+    public void isSessionValid_throwsException() throws Exception
+    {
+        try
+        {
+            afsClient.isSessionValid();
+            fail();
+        } catch (IllegalStateException e)
+        {
+            assertThat(e.getMessage(), containsString("No session information detected!"));
+        }
+    }
+
+    @Test
+    public void isSessionValid_returnsTrue() throws Exception
+    {
+        login();
+
+        final Boolean isValid = afsClient.isSessionValid();
+        assertTrue(isValid);
+    }
+
+    @Test
+    public void logout_withoutLogin_throwsException() throws Exception
+    {
+        try
+        {
+            afsClient.logout();
+            fail();
+        } catch (IllegalStateException e)
+        {
+            assertThat(e.getMessage(), containsString("No session information detected!"));
+        }
+    }
+
+    @Test
+    public void logout_withLogin_returnsTrue() throws Exception
+    {
+        login();
+
+        final Boolean result = afsClient.logout();
+
+        assertTrue(result);
+    }
+
+}
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ServerClientEnvironmentFS.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ServerClientEnvironmentFS.java
index 9807d346b1ff32632c0928e7662c5fb13a88d5d9..441183e65a9f1a1d4093b6b8e8ea53bf5c6265f0 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/ServerClientEnvironmentFS.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/ServerClientEnvironmentFS.java
@@ -15,7 +15,7 @@
  */
 package ch.ethz.sis.afsserver;
 
-import ch.ethz.sis.afsserver.api.PublicAPI;
+import ch.ethz.sis.afsapi.api.PublicAPI;
 import ch.ethz.sis.afsserver.http.impl.NettyHttpServer;
 import ch.ethz.sis.afsserver.server.Server;
 import ch.ethz.sis.afsserver.server.observer.APIServerObserver;
@@ -26,7 +26,7 @@ import ch.ethz.sis.afsserver.worker.ConnectionFactory;
 import ch.ethz.sis.afsserver.worker.WorkerFactory;
 import ch.ethz.sis.afsserver.worker.providers.impl.DummyAuthenticationInfoProvider;
 import ch.ethz.sis.afsserver.worker.providers.impl.DummyAuthorizationInfoProvider;
-import ch.ethz.sis.shared.json.jackson.JacksonObjectMapper;
+import ch.ethz.sis.afsjson.jackson.JacksonObjectMapper;
 import ch.ethz.sis.shared.log.log4j2.Log4J2LogFactory;
 import ch.ethz.sis.shared.startup.Configuration;
 
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java
index abdb9540d5ce683571e9053c3a796f6975efc784..68d8338a2d4925e3128f39e9d7516ed47c70d4f4 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/AbstractPublicAPIWrapper.java
@@ -15,8 +15,8 @@
  */
 package ch.ethz.sis.afsserver.core;
 
-import ch.ethz.sis.afs.api.dto.File;
-import ch.ethz.sis.afsserver.api.PublicAPI;
+import ch.ethz.sis.afsapi.dto.File;
+import ch.ethz.sis.afsapi.api.PublicAPI;
 import lombok.NonNull;
 
 import java.util.List;
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
index 3b9b1f7271dedc856b3a94bc96801c5aac763be4..cb7acab3d1335309a2fce86c32e7595aea087280 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/core/PublicApiTest.java
@@ -15,12 +15,11 @@
  */
 package ch.ethz.sis.afsserver.core;
 
-import ch.ethz.sis.afs.api.dto.File;
+import ch.ethz.sis.afsapi.dto.File;
 import ch.ethz.sis.afsserver.AbstractTest;
 import ch.ethz.sis.afsserver.ServerClientEnvironmentFS;
-import ch.ethz.sis.afsserver.api.PublicAPI;
+import ch.ethz.sis.afsapi.api.PublicAPI;
 import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter;
-import ch.ethz.sis.shared.exception.ThrowableReason;
 import ch.ethz.sis.shared.io.IOUtils;
 import org.junit.After;
 import org.junit.Before;
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java
index 47a3ed56e50c645aa27a7aea6ef441f53b423047..6c6ff9644b4184c00f021f0218a658e015903ef1 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/APIServerAdapterWrapper.java
@@ -23,7 +23,7 @@ import ch.ethz.sis.afsserver.server.impl.ApiResponse;
 import ch.ethz.sis.afsserver.server.impl.ApiServerAdapter;
 import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor;
 import ch.ethz.sis.shared.io.IOUtils;
-import ch.ethz.sis.shared.json.JSONObjectMapper;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.shared.log.LogManager;
 import ch.ethz.sis.shared.log.Logger;
 import io.netty.handler.codec.http.HttpMethod;
@@ -40,9 +40,9 @@ public class APIServerAdapterWrapper extends AbstractPublicAPIWrapper {
     private static final Logger logger = LogManager.getLogger(APIServerAdapterWrapper.class);
 
     private ApiServerAdapter apiServerAdapter;
-    private JSONObjectMapper jsonObjectMapper;
+    private JsonObjectMapper jsonObjectMapper;
 
-    public APIServerAdapterWrapper(ApiServerAdapter apiServerAdapter, JSONObjectMapper jsonObjectMapper) {
+    public APIServerAdapterWrapper(ApiServerAdapter apiServerAdapter, JsonObjectMapper jsonObjectMapper) {
         this.apiServerAdapter = apiServerAdapter;
         this.jsonObjectMapper = jsonObjectMapper;
     }
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java
index feafbd48dd6462343d2ffa919dd1fa43df36e42b..e61913bf65df3447b73cb0669d68c9a09234f642 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerAdapterTest.java
@@ -16,11 +16,11 @@
 package ch.ethz.sis.afsserver.impl;
 
 import ch.ethz.sis.afsserver.ServerClientEnvironmentFS;
-import ch.ethz.sis.afsserver.api.PublicAPI;
+import ch.ethz.sis.afsapi.api.PublicAPI;
 import ch.ethz.sis.afsserver.server.APIServer;
 import ch.ethz.sis.afsserver.server.impl.ApiServerAdapter;
 import ch.ethz.sis.afsserver.startup.AtomicFileSystemServerParameter;
-import ch.ethz.sis.shared.json.JSONObjectMapper;
+import ch.ethz.sis.afsjson.JsonObjectMapper;
 import ch.ethz.sis.shared.startup.Configuration;
 
 public class ApiServerAdapterTest extends ApiServerTest {
@@ -29,7 +29,7 @@ public class ApiServerAdapterTest extends ApiServerTest {
     public PublicAPI getPublicAPI() throws Exception {
         APIServer apiServer = getAPIServer();
         Configuration configuration = ServerClientEnvironmentFS.getInstance().getDefaultServerConfiguration();
-        JSONObjectMapper jsonObjectMapper = configuration.getSharableInstance(AtomicFileSystemServerParameter.jsonObjectMapperClass);
+        JsonObjectMapper jsonObjectMapper = configuration.getSharableInstance(AtomicFileSystemServerParameter.jsonObjectMapperClass);
         ApiServerAdapter apiServerAdapter = new ApiServerAdapter(apiServer, jsonObjectMapper);
         return new APIServerAdapterWrapper(apiServerAdapter, jsonObjectMapper);
     }
diff --git a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java
index 3ea5350afe0ca5c006008601ed1e87280f0522ec..cfe121fae004fa5af61d8db73a47933422c25b64 100644
--- a/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java
+++ b/server-data-store/src/test/java/ch/ethz/sis/afsserver/impl/ApiServerTest.java
@@ -17,7 +17,7 @@ package ch.ethz.sis.afsserver.impl;
 
 import ch.ethz.sis.afs.manager.TransactionConnection;
 import ch.ethz.sis.afsserver.ServerClientEnvironmentFS;
-import ch.ethz.sis.afsserver.api.PublicAPI;
+import ch.ethz.sis.afsapi.api.PublicAPI;
 import ch.ethz.sis.afsserver.core.PublicApiTest;
 import ch.ethz.sis.afsserver.server.APIServer;
 import ch.ethz.sis.afsserver.server.Worker;
diff --git a/server-data-store/src/test/resources/test-server-config.properties b/server-data-store/src/test/resources/test-server-config.properties
new file mode 100644
index 0000000000000000000000000000000000000000..0b988b4160d4a558b42d4d219c337a9e64b0cb6f
--- /dev/null
+++ b/server-data-store/src/test/resources/test-server-config.properties
@@ -0,0 +1,26 @@
+logFactoryClass=ch.ethz.sis.shared.log.log4j2.Log4J2LogFactory
+logConfigFile=
+
+jsonObjectMapperClass=ch.ethz.sis.afsjson.jackson.JacksonObjectMapper
+# Where all the transactions information is written until the prepare step
+# For performance reasons should be on the save volume as the configured storage
+writeAheadLogRoot=./target/tests/transactions
+storageRoot=./target/tests/storage
+
+httpServerClass=ch.ethz.sis.afsserver.http.impl.NettyHttpServer
+httpServerPort=8085
+httpServerUri=/fileserver
+httpMaxContentLength=1024
+
+maxReadSizeInBytes=1024
+authenticationInfoProviderClass=ch.ethz.sis.afsserver.worker.providers.impl.DummyAuthenticationInfoProvider
+authorizationInfoProviderClass=ch.ethz.sis.afsserver.worker.providers.impl.DummyAuthorizationInfoProvider
+poolSize=50
+connectionFactoryClass=ch.ethz.sis.afsserver.worker.ConnectionFactory
+workerFactoryClass=ch.ethz.sis.afsserver.worker.WorkerFactory
+publicApiInterface=ch.ethz.sis.afsapi.api.PublicAPI
+apiServerInteractiveSessionKey=1234
+apiServerTransactionManagerKey=5678
+apiServerWorkerTimeout=30000
+openBISUrl=
+openBISTimeout=30000
\ No newline at end of file