diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionCoordinatorApi.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionCoordinatorApi.java
index b9c04ec29ce7c6efb02640a50273d950200a3a41..c5bf432cdee4bdc58ccd88615fafa01a5fb1fca4 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionCoordinatorApi.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionCoordinatorApi.java
@@ -10,6 +10,8 @@ public interface ITransactionCoordinatorApi extends ITransactionCoordinator, IRp
 
     String SERVICE_URL = "/rmi-" + SERVICE_NAME;
 
+    String JSON_SERVICE_URL = SERVICE_URL + ".json";
+
     String APPLICATION_SERVER_PARTICIPANT_ID = "application-server";
 
     String AFS_SERVER_PARTICIPANT_ID = "afs-server";
diff --git a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionParticipantApi.java b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionParticipantApi.java
index e3920987b4177727a9d7a412c612ddee2e55e66e..c4a03aaa26ce6ce73e3eb960be0fe57df6d2f1de 100644
--- a/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionParticipantApi.java
+++ b/api-openbis-java/source/java/ch/ethz/sis/openbis/generic/asapi/v3/ITransactionParticipantApi.java
@@ -10,4 +10,6 @@ public interface ITransactionParticipantApi extends ITransactionParticipant, IRp
 
     String SERVICE_URL = "/rmi-" + SERVICE_NAME;
 
+    String JSON_SERVICE_URL = SERVICE_URL + ".json";
+
 }
\ No newline at end of file
diff --git a/api-openbis-javascript/src/v3/openbis.js b/api-openbis-javascript/src/v3/openbis.js
index 35085226c7282a60e67c30f8a389475bc6216c75..737c46e6822bd2d2109232e0eaea17337c70bc17 100644
--- a/api-openbis-javascript/src/v3/openbis.js
+++ b/api-openbis-javascript/src/v3/openbis.js
@@ -87,6 +87,32 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria
 			return dfd.promise();
 		};
 
+		this.checkSessionTokenExists = function(){
+		    if (!this.sessionToken)
+            {
+                throw new Error("Session token hasn't been set");
+            }
+		}
+
+		this.checkInteractiveSessionKeyExists = function(){
+            if (!this.interactiveSessionKey)
+            {
+                throw new Error("Interactive session token hasn't been set");
+            }
+		}
+
+        this.checkTransactionDoesNotExist = function(){
+            if (this.transactionId){
+                throw new Error("Operation cannot be executed. Expected no active transactions, but found transaction '" + this.transactionId + "'.");
+            }
+        }
+
+        this.checkTransactionExists = function(){
+            if (!this.transactionId){
+                throw new Error("Operation cannot be executed. No active transaction found.");
+            }
+        }
+
 		this.log = function(msg) {
 			if (console) {
 				console.log(msg);
@@ -484,10 +510,48 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria
 
 	}
 
+    // parseUri 1.2.2 (c) Steven Levithan <stevenlevithan.com> MIT License (see http://blog.stevenlevithan.com/archives/parseuri)
+
+    var parseUri = function(str) {
+        var options = {
+            strictMode: false,
+            key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
+            q:   {
+                name:   "queryKey",
+                parser: /(?:^|&)([^&=]*)=?([^&]*)/g
+            },
+            parser: {
+                strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
+                loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
+            }
+        };
+
+        var	o   = options,
+            m   = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
+            uri = {},
+            i   = 14;
+
+        while (i--) uri[o.key[i]] = m[i] || "";
+
+        uri[o.q.name] = {};
+        uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
+            if ($1) uri[o.q.name][$1] = $2;
+        });
+
+        return uri;
+    }
+
 	var facade = function(openbisUrl, afsUrl) {
 
+        var transactionCoordinatorUrl = "/openbis/openbis/rmi-transaction-coordinator.json";
+
 		if (!openbisUrl) {
 			openbisUrl = "/openbis/openbis/rmi-application-server-v3.json";
+		} else {
+            var openbisUrlParts = parseUri(openbisUrl)
+            if (openbisUrlParts.protocol && openbisUrlParts.authority) {
+                transactionCoordinatorUrl = openbisUrlParts.protocol + "://" + openbisUrlParts.authority + "/openbis/openbis/rmi-transaction-coordinator.json"
+            }
 		}
 
 		this._private = new __private();
@@ -542,6 +606,60 @@ define([ 'jquery', 'util/Json', 'as/dto/datastore/search/DataStoreSearchCriteria
 			});
 		}
 
+		this.setInteractiveSessionKey = function(interactiveSessionKey) {
+		    this._private.interactiveSessionKey = interactiveSessionKey;
+		}
+
+		this.beginTransaction = function() {
+		    var thisFacade = this;
+
+            thisFacade._private.checkTransactionDoesNotExist();
+            thisFacade._private.checkSessionTokenExists();
+            thisFacade._private.checkInteractiveSessionKeyExists();
+
+		    thisFacade._private.transactionId = crypto.randomUUID();
+
+		    return thisFacade._private.ajaxRequest({
+                url : transactionCoordinatorUrl,
+                data : {
+                    "method" : "beginTransaction",
+                    "params" : [ thisFacade._private.transactionId, thisFacade._private.sessionToken, thisFacade._private.interactiveSessionKey ]
+                }
+            });
+		}
+
+		this.commitTransaction = function(){
+		    var thisFacade = this;
+
+		    thisFacade._private.checkTransactionExists();
+            thisFacade._private.checkSessionTokenExists();
+            thisFacade._private.checkInteractiveSessionKeyExists();
+
+		    return thisFacade._private.ajaxRequest({
+                url : transactionCoordinatorUrl,
+                data : {
+                    "method" : "commitTransaction",
+                    "params" : [ thisFacade._private.transactionId, thisFacade._private.sessionToken, thisFacade._private.interactiveSessionKey ]
+                }
+            });
+		}
+
+        this.rollbackTransaction = function(){
+            var thisFacade = this;
+
+		    thisFacade._private.checkTransactionExists();
+            thisFacade._private.checkSessionTokenExists();
+            thisFacade._private.checkInteractiveSessionKeyExists();
+
+		    return thisFacade._private.ajaxRequest({
+                url : transactionCoordinatorUrl,
+                data : {
+                    "method" : "rollbackTransaction",
+                    "params" : [ thisFacade._private.transactionId, thisFacade._private.sessionToken, thisFacade._private.interactiveSessionKey ]
+                }
+            });
+        }
+
 		this.getSessionInformation = function() {
 			var thisFacade = this;
 			return thisFacade._private.ajaxRequest({
diff --git a/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java b/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java
index 62bfcaa899ef2cc16b914efbada6f6b4f1132072..cb1cb28377795d66cf6b5f7992296c72e2738e45 100644
--- a/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java
+++ b/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/dto/OpenBISJavaScriptFacade.java
@@ -299,7 +299,26 @@ public class OpenBISJavaScriptFacade implements IApplicationServerApi
     @TypeScriptMethod
     @Override public void logout(final String sessionToken)
     {
+    }
+
+    @TypeScriptMethod(sessionToken = false)
+    public void setInteractiveSessionKey(String interactiveSessionKey){
+    }
 
+    @TypeScriptMethod(sessionToken = false)
+    public String beginTransaction()
+    {
+        return null;
+    }
+
+    @TypeScriptMethod(sessionToken = false)
+    public void commitTransaction()
+    {
+    }
+
+    @TypeScriptMethod(sessionToken = false)
+    public void rollbackTransaction()
+    {
     }
 
     @TypeScriptMethod
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/TransactionCoordinatorJsonServer.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/TransactionCoordinatorJsonServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bfa11c29ab877df954e1a5e927d95041a48f39b
--- /dev/null
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/TransactionCoordinatorJsonServer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright ETH 2009 - 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.openbis.generic.server.asapi.v3;
+
+import java.io.IOException;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpMethod;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.ITransactionCoordinatorApi;
+import ch.ethz.sis.openbis.generic.server.sharedapi.v3.json.ObjectMapperResource;
+import ch.systemsx.cisd.openbis.common.api.server.AbstractApiJsonServiceExporter;
+
+/**
+ * @author pkupczyk
+ */
+@Controller
+public class TransactionCoordinatorJsonServer extends AbstractApiJsonServiceExporter
+{
+    @Resource(name = ObjectMapperResource.NAME)
+    private ObjectMapper objectMapper;
+
+    @Autowired
+    private ITransactionCoordinatorApi transactionCoordinatorApi;
+
+    @Override
+    public void afterPropertiesSet() throws Exception
+    {
+        setObjectMapper(objectMapper);
+        establishService(ITransactionCoordinatorApi.class, transactionCoordinatorApi, ITransactionCoordinatorApi.SERVICE_NAME,
+                ITransactionCoordinatorApi.JSON_SERVICE_URL);
+        super.afterPropertiesSet();
+    }
+
+    @RequestMapping({ ITransactionCoordinatorApi.JSON_SERVICE_URL, "/openbis" + ITransactionCoordinatorApi.JSON_SERVICE_URL })
+    @Override
+    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException,
+            IOException
+    {
+        if (request.getMethod().equals(HttpMethod.OPTIONS.name()))
+        {
+            return;
+        }
+
+        super.handleRequest(request, response);
+    }
+}
diff --git a/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/TransactionParticipantJsonServer.java b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/TransactionParticipantJsonServer.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f513e774ceac0a2c11920d36649552dd45a89f4
--- /dev/null
+++ b/server-application-server/source/java/ch/ethz/sis/openbis/generic/server/asapi/v3/TransactionParticipantJsonServer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright ETH 2009 - 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.openbis.generic.server.asapi.v3;
+
+import java.io.IOException;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.http.HttpMethod;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import ch.ethz.sis.openbis.generic.asapi.v3.ITransactionParticipantApi;
+import ch.ethz.sis.openbis.generic.server.sharedapi.v3.json.ObjectMapperResource;
+import ch.systemsx.cisd.openbis.common.api.server.AbstractApiJsonServiceExporter;
+
+/**
+ * @author pkupczyk
+ */
+@Controller
+public class TransactionParticipantJsonServer extends AbstractApiJsonServiceExporter
+{
+    @Resource(name = ObjectMapperResource.NAME)
+    private ObjectMapper objectMapper;
+
+    @Autowired
+    private ITransactionParticipantApi transactionParticipantApi;
+
+    @Override
+    public void afterPropertiesSet() throws Exception
+    {
+        setObjectMapper(objectMapper);
+        establishService(ITransactionParticipantApi.class, transactionParticipantApi, ITransactionParticipantApi.SERVICE_NAME,
+                ITransactionParticipantApi.JSON_SERVICE_URL);
+        super.afterPropertiesSet();
+    }
+
+    @RequestMapping({ ITransactionParticipantApi.JSON_SERVICE_URL, "/openbis" + ITransactionParticipantApi.JSON_SERVICE_URL })
+    @Override
+    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
+            throws ServletException,
+            IOException
+    {
+        if (request.getMethod().equals(HttpMethod.OPTIONS.name()))
+        {
+            return;
+        }
+
+        super.handleRequest(request, response);
+    }
+}
diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/main.js b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/main.js
index d6b0d9cd0adcd5200b62d21b7dff6cbdb9680cde..674b2ae9f450ca9edd0ed49b7d037408c5452dca 100644
--- a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/main.js
+++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/main.js
@@ -20,6 +20,7 @@ define([
     "test-compiled/test-import-export",
     "test-compiled/test-typescript",
     "test-compiled/test-afs",
+    "test-compiled/test-transactions",
 ], function () {
     var testSuites = arguments
     return async function () {
diff --git a/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-transactions.ts b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-transactions.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7ea017f02d84cc41d6f3ae78cb09d5f9600a1e82
--- /dev/null
+++ b/test-api-openbis-javascript/servers/common/core-plugins/tests/1/as/webapps/openbis-v3-api-test/html/test/test-transactions.ts
@@ -0,0 +1,44 @@
+import jquery from "./types/jquery"
+import underscore from "./types/underscore"
+import common from "./types/common"
+import openbis from "./types/openbis.esm"
+
+exports.default = new Promise((resolve) => {
+    require(["jquery", "underscore", "openbis", "test/common", "test/dtos"], function (
+        $: jquery.JQueryStatic,
+        _: underscore.UnderscoreStatic,
+        openbisRequireJS,
+        common: common.CommonConstructor,
+        dtos
+    ) {
+        var executeModule = function (moduleName: string, facade: openbis.openbis, dtos: openbis.bundle) {
+            QUnit.module(moduleName)
+
+            QUnit.test("begin() and rollback()", async function (assert) {
+                try {
+                    var c = new common(assert, dtos)
+                    c.start()
+
+                    await c.login(facade)
+
+                    facade.setInteractiveSessionKey("test-interactive-session-key")
+
+                    await facade.beginTransaction()
+
+                    await facade.rollbackTransaction()
+                    c.finish()
+                } catch (error) {
+                    c.fail(error)
+                    c.finish()
+                }
+            })
+        }
+
+        resolve(function () {
+            var afsServerUrl = "http://localhost:8085/data-store-server"
+            executeModule("Transactions tests (RequireJS)", new openbisRequireJS(null, afsServerUrl), dtos)
+            executeModule("Transactions tests (module VAR)", new window.openbis.openbis(null, afsServerUrl), window.openbis)
+            executeModule("Transactions tests (module ESM)", new window.openbisESM.openbis(null, afsServerUrl), window.openbisESM)
+        })
+    })
+})
diff --git a/test-api-openbis-javascript/servers/common/openBIS-server/etc/service.properties b/test-api-openbis-javascript/servers/common/openBIS-server/etc/service.properties
index a2dfb9e879f274a9db6f602b68f0861868358c8c..f69d3687a8d03e5d83cbe6d0878d8c9be0927eef 100644
--- a/test-api-openbis-javascript/servers/common/openBIS-server/etc/service.properties
+++ b/test-api-openbis-javascript/servers/common/openBIS-server/etc/service.properties
@@ -219,4 +219,41 @@ openbisDB2.database-password=
 authorization-component-factory=active-authorization
 script-folder=../../../../server-application-server/source
 jython-version=2.7
-project-samples-enabled=true
\ No newline at end of file
+project-samples-enabled=true
+
+#
+# Transactions
+#
+
+# Global switch to enable or disabled the transaction functionality. Default: false.
+api.v3.transaction.enabled = true
+
+# A secret known only to the transaction coordinator that proves its identity to the transaction participants. Default: a secure random key gets generated at startup.
+api.v3.transaction.coordinator-key = test-transaction-coordinator-key
+
+# A secret known only to chosen users of the API that proves they are allowed to use transactions. Default: a secure random key gets generated at startup.
+api.v3.transaction.interactive-session-key = test-interactive-session-key
+
+# A maximum number of simultaneous transactions. Default: 10.
+# api.v3.transaction.transaction-count-limit =
+
+# A timeout in seconds for transactions. After such an inactivity period a transaction will be regarded as abandoned and will be automatically rolled back. Default: 3600.
+api.v3.transaction.transaction-timeout = 60
+
+# An interval in seconds that controls how often a task that finishes failed and abandoned transactions runs. Default: 600.
+api.v3.transaction.finish-transactions-interval = 15
+
+# A path to a folder where transaction statuses are stored. Default: transaction-logs.
+api.v3.transaction.transaction-log-folder-path = targets/transaction-logs
+
+# An url of the application server that participates in the two phase commit (e.g. https://localhost:8443)
+api.v3.transaction.participant.application-server.url = http://localhost:20000
+
+# A timeout in seconds for the application server operations. Default: 3600
+# api.v3.transaction.participant.application-server.timeout =
+
+# An url of the afs server that participates in the two phase commit (e.g. https://localhost:8085/data-store-server)
+api.v3.transaction.participant.afs-server.url = http://localhost:8085/data-store-server
+
+# A timeout in seconds for the afs server operations. Default: 3600
+# api.v3.transaction.participant.afs-server.timeout =
diff --git a/test-api-openbis-javascript/servers/common/openBIS-server/targets/.gitignore b/test-api-openbis-javascript/servers/common/openBIS-server/targets/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..215427be6b007ff7eb797e4c9c8736565ab139f5
--- /dev/null
+++ b/test-api-openbis-javascript/servers/common/openBIS-server/targets/.gitignore
@@ -0,0 +1 @@
+transaction-logs
\ No newline at end of file