diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandlerV2.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandlerV2.java
index 01244f61ccee0f10755b69febd5139894d204359..5cb098e0a0626802c1fb869b180ff5fdd8154c89 100644
--- a/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandlerV2.java
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/http/impl/NettyHttpHandlerV2.java
@@ -78,7 +78,7 @@ public class NettyHttpHandlerV2 extends ChannelInboundHandlerAdapter
                     }
 
                     HttpResponse apiResponse = httpServerHandler.process(request.method(),
-                            queryStringDecoderForParameters.parameters(), array);
+                            queryStringDecoderForParameters.parameters(), null);
                     HttpResponseStatus status = (!apiResponse.isError()) ?
                             HttpResponseStatus.OK :
                             HttpResponseStatus.BAD_REQUEST;
diff --git a/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapterV2.java b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapterV2.java
new file mode 100644
index 0000000000000000000000000000000000000000..2b8806784208f9891f3c151b32206eb1f30deb85
--- /dev/null
+++ b/server-data-store/src/main/java/ch/ethz/sis/afsserver/server/impl/ApiServerAdapterV2.java
@@ -0,0 +1,239 @@
+/*
+ * 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.afsserver.server.impl;
+
+import ch.ethz.sis.afsjson.JsonObjectMapper;
+import ch.ethz.sis.afsserver.exception.HTTPExceptions;
+import ch.ethz.sis.afsserver.http.HttpResponse;
+import ch.ethz.sis.afsserver.http.HttpServerHandler;
+import ch.ethz.sis.afsserver.server.APIServer;
+import ch.ethz.sis.afsserver.server.APIServerException;
+import ch.ethz.sis.afsserver.server.Request;
+import ch.ethz.sis.afsserver.server.Response;
+import ch.ethz.sis.afsserver.server.performance.Event;
+import ch.ethz.sis.afsserver.server.performance.PerformanceAuditor;
+import ch.ethz.sis.shared.log.LogManager;
+import ch.ethz.sis.shared.log.Logger;
+import io.netty.handler.codec.http.HttpMethod;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+import static io.netty.handler.codec.http.HttpMethod.*;
+
+/*
+ * This class is supposed to be called by a TCP or HTTP transport class
+ */
+public class ApiServerAdapterV2<CONNECTION, API> implements HttpServerHandler
+{
+
+    private static final Logger logger = LogManager.getLogger(ApiServerAdapterV2.class);
+
+    private final APIServer<CONNECTION, Request, Response, API> server;
+
+    private final JsonObjectMapper jsonObjectMapper;
+
+    private final ApiResponseBuilder apiResponseBuilder;
+
+    public ApiServerAdapterV2(
+            APIServer<CONNECTION, Request, Response, API> server,
+            JsonObjectMapper jsonObjectMapper)
+    {
+        this.server = server;
+        this.jsonObjectMapper = jsonObjectMapper;
+        this.apiResponseBuilder = new ApiResponseBuilder();
+    }
+
+    public static HttpMethod getHttpMethod(String apiMethod)
+    {
+        switch (apiMethod)
+        {
+            case "list":
+            case "read":
+            case "isSessionValid":
+                return GET; // all parameters from GET methods come on the query string
+            case "write":
+            case "move":
+            case "copy":
+            case "login":
+            case "logout":
+            case "begin":
+            case "prepare":
+            case "commit":
+            case "rollback":
+            case "recover":
+                return POST; // all parameters from POST methods come on the body
+            case "delete":
+                return HttpMethod.DELETE; // all parameters from DELETE methods come on the body
+        }
+        throw new UnsupportedOperationException("This line SHOULD NOT be unreachable!");
+    }
+
+    public boolean isValidMethod(HttpMethod givenMethod, String apiMethod)
+    {
+        HttpMethod correctMethod = getHttpMethod(apiMethod);
+        return correctMethod == givenMethod;
+    }
+
+    public HttpResponse process(HttpMethod httpMethod, Map<String, List<String>> parameters,
+            byte[] requestBody)
+    {
+        try
+        {
+            logger.traceAccess(null);
+            PerformanceAuditor performanceAuditor = new PerformanceAuditor();
+
+
+            if (httpMethod != GET && httpMethod != POST && httpMethod != DELETE)
+            {
+                return getHTTPResponse(new ApiResponse("1", null,
+                        HTTPExceptions.INVALID_HTTP_METHOD.getCause()));
+            }
+
+            String method = null;
+            String sessionToken = null;
+            String interactiveSessionKey = null;
+            String transactionManagerKey = null;
+            Map<String, Object> methodParameters = new HashMap<>();
+
+            for (Map.Entry<String, List<String>> entry : parameters.entrySet())
+            {
+                String value = null;
+                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()));
+                    }
+                }
+
+                try
+                {
+                    switch (entry.getKey())
+                    {
+                        case "method":
+                            method = value;
+                            if (!isValidMethod(httpMethod, method))
+                            {
+                                return getHTTPResponse(new ApiResponse("1", null,
+                                        HTTPExceptions.INVALID_HTTP_METHOD.getCause()));
+                            }
+                            break;
+                        case "sessionToken":
+                            sessionToken = value;
+                            break;
+                        case "interactiveSessionKey":
+                            interactiveSessionKey = value;
+                            break;
+                        case "transactionManagerKey":
+                            transactionManagerKey = value;
+                            break;
+                        case "transactionId":
+                            methodParameters.put(entry.getKey(), UUID.fromString(value));
+                            break;
+                        case "recursively":
+                            methodParameters.put(entry.getKey(), Boolean.valueOf(value));
+                            break;
+                        case "offset":
+                            methodParameters.put(entry.getKey(), Long.valueOf(value));
+                            break;
+                        case "limit":
+                            methodParameters.put(entry.getKey(), Integer.valueOf(value));
+                            break;
+                        case "data":
+                            methodParameters.put(entry.getKey(), Base64.getDecoder().decode(value));
+                            break;
+                        case "md5Hash":
+                            methodParameters.put(entry.getKey(), Base64.getDecoder().decode(value));
+                            break;
+                        default:
+                            methodParameters.put(entry.getKey(), value);
+                            break;
+                    }
+                } catch (Exception e)
+                {
+                    logger.catching(e);
+                    return getHTTPResponse(new ApiResponse("1", null,
+                            HTTPExceptions.INVALID_PARAMETERS.getCause(
+                                    e.getClass().getSimpleName(),
+                                    e.getMessage())));
+                }
+            }
+
+            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)
+        {
+            logger.catching(e);
+            switch (e.getType())
+            {
+                case MethodNotFound:
+                case IncorrectParameters:
+                case InternalError:
+                    try
+                    {
+                        return getHTTPResponse(new ApiResponse("1", null, e.getData()));
+                    } catch (Exception ex)
+                    {
+                        logger.catching(ex);
+                    }
+            }
+        } catch (Exception e)
+        {
+            logger.catching(e);
+            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.
+    }
+
+    public HttpResponse getHTTPResponse(Response response)
+            throws Exception
+    {
+        boolean error = response.getError() != null;
+        String contentType = null;
+        byte[] body = null;
+        if (response.getResult() instanceof List) {
+            contentType = HttpResponse.CONTENT_TYPE_JSON;
+            body = jsonObjectMapper.writeValue(response);
+        } else if(response.getResult() instanceof byte[]) {
+            contentType = HttpResponse.CONTENT_TYPE_BINARY_DATA;
+            body = (byte[]) response.getResult();
+        } else {
+            contentType = HttpResponse.CONTENT_TYPE_TEXT;
+            body = String.valueOf(response.getResult()).getBytes(StandardCharsets.UTF_8);
+        }
+        return new HttpResponse(error, contentType, body);
+    }
+
+}
\ No newline at end of file