diff --git a/CMakeLists.txt b/CMakeLists.txt
index 090c8233836ad3908e7d327a8149f51a9b19059b..33cda788b2857355d27e6935623c27b33e4111dd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,7 +20,7 @@
 
 cmake_minimum_required(VERSION 3.14)
 
-set(libcaosdb_VERSION 0.0.3)
+set(libcaosdb_VERSION 0.0.4)
 
 project(libcaosdb
     VERSION ${libcaosdb_VERSION}
@@ -57,9 +57,87 @@ add_subdirectory(src)
 add_subdirectory(include)
 add_subdirectory(doc)
 
-add_library(caosdb STATIC ${libcaosdb_INCL} ${libcaosdb_SRC})
+####################################################################
+### CODE GENERATION (WITH GRPC)
+####################################################################
+
+# Protobuf/Grpc source files
+set(PROTO_FILES
+    ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/info/v1alpha1/main.proto
+    ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/entity/v1alpha1/main.proto
+)
+
+set(PROTO_PATH ${PROJECT_SOURCE_DIR}/proto/proto)
+
+# compiler binaries
+set(_PROTOBUF_PROTOC "${CMAKE_BINARY_DIR}/build_tools/protoc")
+set(_GRPC_CPP_PLUGIN_EXECUTABLE "${CMAKE_BINARY_DIR}/build_tools/grpc_cpp_plugin")
+
+# Generated sources
+list(LENGTH PROTO_FILES len_proto_files)
+math(EXPR len_proto_files "${len_proto_files} - 1")
+foreach(i RANGE "${len_proto_files}")
+    list(GET PROTO_FILES ${i} next_proto_file)
+
+    # strip away the prefix path and the ".proto" suffix
+    string(REPLACE
+        "${PROJECT_SOURCE_DIR}/proto/proto/caosdb/"
+        ""
+        next_proto_module
+        "${next_proto_file}")
+    string(REPLACE
+        ".proto"
+        ""
+        next_proto_module
+        "${next_proto_module}")
+    set(next_proto_src
+        "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${next_proto_module}.pb.cc")
+    set(next_proto_hdr
+        "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${next_proto_module}.pb.h")
+    set(next_grpc_src
+        "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${next_proto_module}.grpc.pb.cc")
+    set(next_grpc_hdr
+        "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${next_proto_module}.grpc.pb.h")
+    list(APPEND GRPC_GENERATED_HEADERS "${next_proto_hdr}" "${next_grpc_hdr}")
+    list(APPEND GRPC_GENERATED_SOURCES "${next_proto_src}" "${next_grpc_src}")
+
+endforeach()
+
+set(GRPC_GENERATED
+    ${GRPC_GENERATED_SOURCES}
+    ${GRPC_GENERATED_HEADERS})
+add_custom_command(
+      OUTPUT ${GRPC_GENERATED}
+      COMMAND ${_PROTOBUF_PROTOC}
+      ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}/include"
+        --cpp_out "${CMAKE_CURRENT_BINARY_DIR}/include"
+        -I "${PROTO_PATH}"
+        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
+        ${PROTO_FILES}
+      DEPENDS ${PROTO_FILES})
+
+
+if("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
+    add_library(caosdb_grpc STATIC ${GRPC_GENERATED})
+    add_library(caosdb STATIC ${libcaosdb_INCL} ${libcaosdb_SRC})
+    target_link_libraries(caosdb caosdb_grpc)
+    set(LIBCAOSDB caosdb caosdb_grpc)
+else()
+    add_library(caosdb
+        STATIC ${libcaosdb_INCL} ${libcaosdb_SRC} ${GRPC_GENERATED})
+    set(LIBCAOSDB caosdb)
+endif()
 add_executable(caosdbcli src/caosdbcli.cpp)
 
+target_link_libraries(caosdb
+    ${CONAN_LIBS}
+)
+
+target_link_libraries(caosdbcli
+    ${LIBCAOSDB}
+    ${CONAN_LIBS}
+)
+
 #######################################################
 ### LINTING with CLANG-TIDY and INCLUDE-WHAT-YOU-USE
 #######################################################
@@ -82,6 +160,13 @@ if(_LINTING)
     else()
         message(STATUS "include-what-you-use: ${iwyu}")
         set(_CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${iwyu} "-Xiwyu" "--cxx17ns")
+
+        set_target_properties(caosdb PROPERTIES
+            CXX_INCLUDE_WHAT_YOU_USE "${_CMAKE_CXX_INCLUDE_WHAT_YOU_USE}"
+            )
+        set_target_properties(caosdbcli PROPERTIES
+            CXX_INCLUDE_WHAT_YOU_USE "${_CMAKE_CXX_INCLUDE_WHAT_YOU_USE}"
+            )
     endif()
 
     find_program(clang_tidy NAMES clang-tidy clang-tidy-11)
@@ -96,17 +181,17 @@ if(_LINTING)
             "--warnings-as-errors=*")
         option(AUTO_FIX_LINTING "Append --fix option to clang-tidy" OFF)
         if(AUTO_FIX_LINTING)
-            set(APPEND AUTO_FIX_LINTING "--fix")
+            set(_CMAKE_CXX_CLANG_TIDY "${_CMAKE_CXX_CLANG_TIDY};--fix")
         endif()
+        message(STATUS "Using clang-tidy with
+            '${_CMAKE_CXX_CLANG_TIDY};${_CMAKE_CXX_CLANG_TIDY_CHECKS}'")
+        set_target_properties(caosdb PROPERTIES
+            CXX_CLANG_TIDY "${_CMAKE_CXX_CLANG_TIDY};${_CMAKE_CXX_CLANG_TIDY_CHECKS}"
+            )
+        set_target_properties(caosdbcli PROPERTIES
+            CXX_CLANG_TIDY "${_CMAKE_CXX_CLANG_TIDY};${_CMAKE_CXX_CLANG_TIDY_CHECKS}"
+            )
     endif()
-    set_target_properties(caosdb PROPERTIES
-        CXX_CLANG_TIDY "${_CMAKE_CXX_CLANG_TIDY};${_CMAKE_CXX_CLANG_TIDY_CHECKS}"
-        CXX_INCLUDE_WHAT_YOU_USE "${_CMAKE_CXX_INCLUDE_WHAT_YOU_USE}"
-        )
-    set_target_properties(caosdbcli PROPERTIES
-        CXX_CLANG_TIDY "${_CMAKE_CXX_CLANG_TIDY};${_CMAKE_CXX_CLANG_TIDY_CHECKS}"
-        CXX_INCLUDE_WHAT_YOU_USE "${_CMAKE_CXX_INCLUDE_WHAT_YOU_USE}"
-        )
 endif()
 
 
@@ -119,73 +204,6 @@ if("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
     add_subdirectory(test)
 endif()
 
-####################################################################
-### CODE GENERATION (WITH GRPC)
-####################################################################
-
-# Protobuf/Grpc source files
-set(PROTO_FILES
-    ${PROJECT_SOURCE_DIR}/proto/proto/caosdb/info/v1alpha1/main.proto
-)
-
-set(PROTO_PATH ${PROJECT_SOURCE_DIR}/proto/proto)
-
-# compiler binaries
-set(_PROTOBUF_PROTOC "${CMAKE_BINARY_DIR}/build_tools/protoc")
-set(_GRPC_CPP_PLUGIN_EXECUTABLE "${CMAKE_BINARY_DIR}/build_tools/grpc_cpp_plugin")
-
-# Generated sources
-set(hw_hdrs_path "info/v1alpha1")
-set(hw_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${hw_hdrs_path}/main.pb.cc")
-set(hw_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${hw_hdrs_path}/main.pb.h")
-set(hw_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${hw_hdrs_path}/main.grpc.pb.cc")
-set(hw_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/include/caosdb/${hw_hdrs_path}/main.grpc.pb.h")
-
-# compile *proto files to cpp
-set(GRPC_GENERATED_HEADERS
-    "${hw_proto_hdrs}"
-    "${hw_grpc_hdrs}")
-set(GRPC_GENERATED_SOURCES
-    "${hw_proto_srcs}"
-    "${hw_grpc_srcs}")
-set(GRPC_GENERATED
-    ${GRPC_GENERATED_SOURCES}
-    ${GRPC_GENERATED_HEADERS})
-add_custom_command(
-      OUTPUT ${GRPC_GENERATED}
-      COMMAND ${_PROTOBUF_PROTOC}
-      ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}/include"
-        --cpp_out "${CMAKE_CURRENT_BINARY_DIR}/include"
-        -I "${PROTO_PATH}"
-        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
-        "${PROTO_FILES}"
-      DEPENDS "${PROTO_FILES}")
-
-
-# hw_grpc_proto
-add_library(caosdb_info_v1alpha1
-    ${GRPC_GENERATED})
-target_link_libraries(caosdb_info_v1alpha1
-  ${CONAN_LIBS})
-target_include_directories(caosdb_info_v1alpha1 PUBLIC
-   # headers to include when building from source
-   ${CONAN_INCLUDE_DIRS}
-   $<BUILD_INTERFACE:${libcaosdb_SOURCE_DIR}/include>
-   $<BUILD_INTERFACE:${libcaosdb_BINARY_DIR}/include>
-   $<INSTALL_INTERFACE:include>
-   )
-
-target_link_libraries(caosdb
-  caosdb_info_v1alpha1
-  ${CONAN_LIBS}
-)
-target_link_libraries(caosdbcli
-  caosdb
-  caosdb_info_v1alpha1
-  ${CONAN_LIBS}
-)
-
-
 ###############################################
 ############ INSTALLATION #####################
 ###############################################
@@ -193,59 +211,36 @@ target_link_libraries(caosdbcli
 set(libcaosdb_INCLUDE_DEST "include/caosdb")
 set(libcaosdb_LIB_DEST "lib")
 
-
-# generator expressions are needed for the include directories, since
-# installing headers changes the include path specify that libcaosdb requires
-# the files located in the include/ directory at compile time. This would
-# normally look like
-#   target_include_directories(caosdb PUBLIC include/)
-# PUBLIC means that other libraries including caosdb should also include
-# the directory include/.
-# However, there is a catch. If we are installing the project in
-# CMAKE_INSTALL_PREFIX, we can't specify include/ in the build directory: we
-# have copied the contents of include to CMAKE_INSTALL_PREFIX/include and we
-# would like  other projects to include this directory instead of include/.
-# The following CMake command handles this. $<BUILD_INTERFACE:...> and
-# $<INSTALL_INTERFACE:...> are macros whose values change depending on if we
-# are simply building the code or if we are installing it.
 target_include_directories(caosdb PUBLIC
-   # headers to include when building from source
-   $<BUILD_INTERFACE:${libcaosdb_SOURCE_DIR}/include>
-   $<BUILD_INTERFACE:${libcaosdb_BINARY_DIR}/include>
+    # headers to include when building from source
+    $<BUILD_INTERFACE:${libcaosdb_SOURCE_DIR}/include>
+    $<BUILD_INTERFACE:${libcaosdb_BINARY_DIR}/include>
 
-   # headers to include when installing  (implicitly prefixes with ${CMAKE_INSTALL_PREFIX}).
-   $<INSTALL_INTERFACE:include>
-   )
+    # headers to include when installing  (implicitly prefixes with ${CMAKE_INSTALL_PREFIX}).
+    $<INSTALL_INTERFACE:include>
+)
+
+if("${CMAKE_BUILD_TYPE}" MATCHES "Debug")
+    target_include_directories(caosdb_grpc PUBLIC
+        # headers to include when building from source
+        $<BUILD_INTERFACE:${libcaosdb_SOURCE_DIR}/include>
+        $<BUILD_INTERFACE:${libcaosdb_BINARY_DIR}/include>
+
+        # headers to include when installing  (implicitly prefixes with ${CMAKE_INSTALL_PREFIX}).
+        $<INSTALL_INTERFACE:include>
+     )
+endif()
 
 target_include_directories(caosdbcli PUBLIC
    ${libcaosdb_SOURCE_DIR}/include>
    ${libcaosdb_BINARY_DIR}/include>
    ${CONAN_INCLUDE_DIRS}
-   )
+)
 
 set(CMAKE_INSTALL_PREFIX "$ENV{HOME}/.local/")
-# Install libcaosdb in CMAKE_INSTALL_PREFIX (defaults to /usr/local on linux).
-# To change the install location, run
-#   cmake -DCMAKE_INSTALL_PREFIX=<desired-install-path> ..
-
-# install(...) specifies installation rules for the project. It can specify
-# location of installed files on the system, user permissions, build
-# configurations, etc. Here, we are only copying files.
-# install(TARGETS ...) specifies rules for installing targets.
-# Here, we are taking a target or list of targets (libcosdb) and telling
-# CMake the following:
-#   - put shared libraries associated with libcaosdb in ${libcaosdb_LIB_DEST}
-#   - put static libraries associated with libcaosdb in ${libcaosdb_LIB_DEST}
-#   - put include files associated with libcaosdb in ${libcaosdb_INCLUDE_DEST}
-# We also need to specify the export that is associated with libcaosdb; an
-# export is just a list of targets to be installed. So we are associating
-# libcaosdb with libcaosdbTargets.
 install(
     # targets to install
-    TARGETS
-        caosdb
-        caosdb_info_v1alpha1
-        ${_GRPC_DEPS}
+    TARGETS ${LIBCAOSDB}
     # name of the CMake "export group" containing the targets we want to install
     EXPORT caosdbTargets
     # Dynamic, static library and include destination locations after running
@@ -253,19 +248,9 @@ install(
     LIBRARY DESTINATION ${libcaosdb_LIB_DEST}
     ARCHIVE DESTINATION ${libcaosdb_LIB_DEST}
     INCLUDES DESTINATION ${libcaosdb_INCLUDE_DEST}
-    )
-
-
-# We now need to install the export libcaosdbTargets that we defined above.
-# This is needed in order for another project to import libcaosdb using
-#   find_package(caosdb)
-# find_package(caosdb) will look for caosdb-config.cmake to provide
-# information about the targets contained in the project libcaosdb.
-# Fortunately, this is specified in the export libcaosdbTargets, so we will
-# install this too.
-# install(EXPORT ...) will install the information about an export. Here, we
-# save it to a file {$libcaosdb_LIB_DEST}/libcaosdbTargets.cmake and prepend
-# everything inside libcaosdbTargets  with the namespace libcaosdb::.
+)
+
+
 set(libcaosdb_CMAKE_DEST "${libcaosdb_LIB_DEST}/cmake/caosdb")
 install(
     # The export we want to save (matches name defined above containing the
@@ -278,10 +263,8 @@ install(
     NAMESPACE caosdb::
     # where to place the resulting file (here, we're putting it with the library)
     DESTINATION ${libcaosdb_CMAKE_DEST}
-    )
+)
 
-## install(FILES ...) simply puts files in a certain place with certain
-## properties. We're just copying them to the desired place here.
 install(FILES ${libcaosdb_INCL} DESTINATION ${libcaosdb_INCLUDE_DEST})
 install(FILES ${GRPC_GENERATED_HEADERS}
     DESTINATION ${libcaosdb_INCLUDE_DEST}/${hw_hdrs_path})
@@ -310,4 +293,3 @@ if(AUTOFORMATTING)
         ${format_test_sources}
         WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
 endif()
-
diff --git a/conanfile.py b/conanfile.py
index 947b59f702f094799b98713045dcb44bbca3488b..b32c78388637335d93b03bd972e6e5fed5c6dd3b 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -3,7 +3,7 @@ from conans import ConanFile, CMake, tools
 
 class CaosdbConan(ConanFile):
     name = "caosdb"
-    version = "0.0.3"
+    version = "0.0.4"
     license = "AGPL-3.0-or-later"
     author = "Timm C. Fitschen <t.fitschen@indiscale.com>"
     url = "https://gitlab.indiscale.com/caosdb/src/caosdb-cpplib.git"
@@ -47,4 +47,4 @@ class CaosdbConan(ConanFile):
         self.copy("*.a", dst="lib", keep_path=False)
 
     def package_info(self):
-        self.cpp_info.libs = ["caosdb", "caosdb_info_v1alpha1"]
+        self.cpp_info.libs = ["caosdb"]
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index a71690fae1670511fdf270f9941239404423d8cb..89657f1b2ac9c4798b9a1d744416cb2a0125a298 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -20,12 +20,14 @@
 
 # add all header files to this list
 set(libcaosdb_INCL
-    ${CMAKE_CURRENT_BINARY_DIR}/caosdb/constants.h
-    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/connection.h
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/authentication.h
-    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/utils.h
-    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/info.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/connection.h
+    ${CMAKE_CURRENT_BINARY_DIR}/caosdb/constants.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/entity.h
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/exceptions.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/info.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/utils.h
     )
 
 # pass variable to parent scope
diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h
index 7c971c0cc738447942b1d6a511ce4af4e83264d6..a58f043adebc0ea8c1f0b3edd8346aae0b9dbdf5 100644
--- a/include/caosdb/connection.h
+++ b/include/caosdb/connection.h
@@ -27,10 +27,13 @@
  * @date 2021-05-18
  * @brief Configuration and setup of the connection.
  */
-#include <iosfwd>                              // for ostream
-#include <memory>                              // for shared_ptr, unique_ptr
-#include <string>                              // for string
-#include "caosdb/info/v1alpha1/main.grpc.pb.h" // for GeneralInfoService
+#include <iosfwd> // for ostream
+#include <memory> // for shared_ptr, unique_ptr
+#include <string> // for string
+#include "caosdb/transaction.h"
+#include "caosdb/info/v1alpha1/main.grpc.pb.h"   // for GeneralInfoService
+#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionService
+
 namespace caosdb {
 namespace authentication {
 class Authenticator;
@@ -40,13 +43,16 @@ class VersionInfo;
 } // namespace info
 } // namespace caosdb
 namespace grpc {
+class Channel;
 class ChannelCredentials;
 } // namespace grpc
 
 namespace caosdb::connection {
 using caosdb::authentication::Authenticator;
+using caosdb::entity::v1alpha1::EntityTransactionService;
 using caosdb::info::VersionInfo;
 using caosdb::info::v1alpha1::GeneralInfoService;
+using caosdb::transaction::Transaction;
 using grpc::ChannelCredentials;
 
 class CACertificateProvider {
@@ -131,16 +137,16 @@ public:
 class CaosDBConnection {
   std::shared_ptr<grpc::Channel> channel;
   std::shared_ptr<CaosDBConnectionConfig> config;
-  std::unique_ptr<GeneralInfoService::Stub> stub_;
+  std::unique_ptr<GeneralInfoService::Stub> general_info_service;
+  std::shared_ptr<EntityTransactionService::Stub> entity_transaction_service;
 
 public:
   explicit CaosDBConnection(
     const std::shared_ptr<CaosDBConnectionConfig> &config);
   friend auto operator<<(std::ostream &out, const CaosDBConnection &connection)
     -> std::ostream &;
-  [[nodiscard]] auto getGeneralInfoService() const
-    -> GeneralInfoService::Stub &;
-  [[nodiscard]] auto getVersionInfo() const -> std::unique_ptr<VersionInfo>;
+  [[nodiscard]] auto GetVersionInfo() const -> std::unique_ptr<VersionInfo>;
+  [[nodiscard]] auto CreateTransaction() const -> std::unique_ptr<Transaction>;
 };
 } // namespace caosdb::connection
 #endif
diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
new file mode 100644
index 0000000000000000000000000000000000000000..d9bdf0663e5152753bb79f85ffbcecc70816ae79
--- /dev/null
+++ b/include/caosdb/entity.h
@@ -0,0 +1,58 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * @brief Anything entity-related.
+ */
+#ifndef CAOSDB_ENTITY_H
+#define CAOSDB_ENTITY_H
+
+#include <string>
+
+namespace caosdb::entity {
+
+/**
+ * Entity ID.
+ */
+class EntityID {
+private:
+  std::string id;
+
+public:
+  EntityID(std::string id);
+  [[nodiscard]] auto ToString() const -> std::string;
+};
+
+/**
+ * @brief Wrapper for the Protobuf entity.
+ */
+class Entity {
+private:
+  EntityID id;
+
+public:
+  [[nodiscard]] auto GetId() const -> EntityID;
+  auto SetId(EntityID id) -> void;
+  [[nodiscard]] auto GetName() const -> std::string;
+};
+
+} // namespace caosdb::entity
+#endif
diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h
new file mode 100644
index 0000000000000000000000000000000000000000..552f1f06362fe0d59fa99f05a21f34913ce29f6f
--- /dev/null
+++ b/include/caosdb/transaction.h
@@ -0,0 +1,75 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * @brief Creation and execution of transactions.
+ */
+
+#ifndef CAOSDB_TRANSACTION_H
+#define CAOSDB_TRANSACTION_H
+
+#include <memory>
+#include "caosdb/entity.h"
+#include "caosdb/entity/v1alpha1/main.pb.h"
+#include "caosdb/entity/v1alpha1/main.grpc.pb.h"
+
+namespace caosdb::transaction {
+using caosdb::entity::Entity;
+using caosdb::entity::EntityID;
+using ProtoEntity = caosdb::entity::v1alpha1::Entity;
+using caosdb::entity::v1alpha1::EntityTransactionService;
+using caosdb::entity::v1alpha1::RetrieveRequest;
+
+class ResultSet {
+public:
+  [[nodiscard]] auto GetById(EntityID id) const -> Entity;
+};
+
+class UniqueResult : public ResultSet {
+private:
+  Entity entity;
+
+public:
+  UniqueResult(ProtoEntity entity);
+};
+
+enum TransactionState { INIT = 10, EXECUTING = 20, SUCCESS = 30, ERROR = 40 };
+
+/**
+ * @brief Create a transaction via `CaosDBConnection.createTransaction()`
+ */
+class Transaction {
+private:
+  std::shared_ptr<ResultSet> result_set;
+  TransactionState state = TransactionState::INIT;
+  std::shared_ptr<EntityTransactionService::Stub> service_stub;
+  RetrieveRequest request; // TODO(tf)
+
+public:
+  Transaction(std::shared_ptr<EntityTransactionService::Stub> service_stub);
+  auto Retrieve(const EntityID &id) -> void;
+  auto Execute() -> void;
+  [[nodiscard]] auto GetResultSet() const -> std::shared_ptr<ResultSet>;
+  auto WaitForIt() const -> void;
+};
+
+} // namespace caosdb::transaction
+#endif
diff --git a/proto b/proto
index b0cade5e02daff99eaa46e1201f2786ac873114b..dd9abe3499367c4eded4774ade1acbc90ef048b3 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit b0cade5e02daff99eaa46e1201f2786ac873114b
+Subproject commit dd9abe3499367c4eded4774ade1acbc90ef048b3
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5f7f41cca95200ede736b84694a36b16388644cd..2ea6daa9136baffb1662dc2fe95aefe382265c62 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -23,7 +23,9 @@
 set(libcaosdb_SRC
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/authentication.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/connection.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/entity.cpp
     ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/info.cpp
+    ${CMAKE_CURRENT_SOURCE_DIR}/caosdb/transaction.cpp
     )
 
 # pass variable to parent scope
diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp
index 81339c2d397c70b413fd7da32a9a000bfed52810..24bf8e72fa7b19756964123a1db71000c492af86 100644
--- a/src/caosdb/connection.cpp
+++ b/src/caosdb/connection.cpp
@@ -20,32 +20,33 @@
  *
  */
 #include "caosdb/connection.h"
-#include <grpcpp/create_channel.h>                // for CreateChannel
-#include <grpcpp/impl/codegen/client_context.h>   // for ClientContext
-#include <grpcpp/impl/codegen/status.h>           // for Status
-#include <grpcpp/security/credentials.h>          // for SslCredentials
-#include <iostream>                               // for operator<<, basic_...
-#include <stdexcept>                              // for runtime_error
-#include <string>                                 // for operator+, char_tr...
+#include <grpcpp/create_channel.h>              // for CreateChannel
+#include <grpcpp/impl/codegen/client_context.h> // for ClientContext
+#include <grpcpp/impl/codegen/status.h>         // for Status
+#include <grpcpp/security/credentials.h>        // for SslCredentials
+#include <iostream>                             // for operator<<, basic_...
+#include <stdexcept>                            // for runtime_error
+#include <string>                               // for operator+, char_tr...
+#include <memory>
 #include "caosdb/authentication.h"                // for Authenticator
 #include "caosdb/exceptions.h"                    // for AuthenticationError
 #include "caosdb/info/v1alpha1/main.grpc.pb.h"    // for GeneralInfoService
 #include "caosdb/info/v1alpha1/main.pb.h"         // for GetVersionInfoResp...
+#include "caosdb/transaction.h"                   // for Transaction
 #include "caosdb/utils.h"                         // for load_string_file
 #include "caosdb/info.h"                          // for VersionInfo
 #include "grpcpp/impl/codegen/status_code_enum.h" // for StatusCode, UNAUTH...
-namespace grpc {
-class Channel;
-} // namespace grpc
 
 namespace caosdb::connection {
 using caosdb::authentication::Authenticator;
+using caosdb::entity::v1alpha1::EntityTransactionService;
 using caosdb::exceptions::AuthenticationError;
 using caosdb::exceptions::ConnectionError;
 using caosdb::info::VersionInfo;
 using caosdb::info::v1alpha1::GeneralInfoService;
 using caosdb::info::v1alpha1::GetVersionInfoRequest;
 using caosdb::info::v1alpha1::GetVersionInfoResponse;
+using caosdb::transaction::Transaction;
 using caosdb::utils::load_string_file;
 using grpc::InsecureChannelCredentials;
 using grpc::SslCredentials;
@@ -149,7 +150,9 @@ CaosDBConnection::CaosDBConnection(
     this->config->getHost() + ":" + std::to_string(this->config->getPort());
   this->channel =
     grpc::CreateChannel(target, this->config->getChannelCredentials());
-  this->stub_ = GeneralInfoService::NewStub(this->channel);
+  this->general_info_service = GeneralInfoService::NewStub(this->channel);
+  this->entity_transaction_service =
+    std::make_shared<EntityTransactionService::Stub>(this->channel);
 }
 
 auto operator<<(std::ostream &out, const CaosDBConnection &connection)
@@ -158,13 +161,13 @@ auto operator<<(std::ostream &out, const CaosDBConnection &connection)
   return out;
 }
 
-[[nodiscard]] auto CaosDBConnection::getVersionInfo() const
+[[nodiscard]] auto CaosDBConnection::GetVersionInfo() const
   -> std::unique_ptr<VersionInfo> {
   const GetVersionInfoRequest request;
   GetVersionInfoResponse response;
   grpc::ClientContext context;
   const grpc::Status status =
-    this->stub_->GetVersionInfo(&context, request, &response);
+    this->general_info_service->GetVersionInfo(&context, request, &response);
 
   if (!status.ok()) {
     switch (status.error_code()) {
@@ -180,4 +183,10 @@ auto operator<<(std::ostream &out, const CaosDBConnection &connection)
   return std::make_unique<VersionInfo>(response.release_version_info());
 }
 
+[[nodiscard]] auto CaosDBConnection::CreateTransaction() const
+  -> std::unique_ptr<Transaction> {
+  auto service_stub = this->entity_transaction_service;
+  return std::make_unique<Transaction>(service_stub);
+};
+
 } // namespace caosdb::connection
diff --git a/src/caosdb/entity.cpp b/src/caosdb/entity.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc3cbf3018b73729591c64b21f58ef5f8de24fa4
--- /dev/null
+++ b/src/caosdb/entity.cpp
@@ -0,0 +1,38 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "caosdb/entity.h"
+
+#include <utility>
+
+namespace caosdb::entity {
+
+EntityID::EntityID(std::string id) { this->id = std::move(id); }
+
+[[nodiscard]] auto EntityID::ToString() const -> std::string {
+  return this->id;
+}
+
+[[nodiscard]] auto Entity::GetId() const -> EntityID { return this->id; }
+
+auto Entity::SetId(EntityID id) -> void { this->id = std::move(id); }
+
+} // namespace caosdb::entity
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1b0faad055eafed39b0cd49aad81c253bec6a1c9
--- /dev/null
+++ b/src/caosdb/transaction.cpp
@@ -0,0 +1,89 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "caosdb/entity.h"
+#include "caosdb/exceptions.h" // for AuthenticationError
+#include "caosdb/transaction.h"
+#include <memory>
+#include <utility>
+#include <iostream>                               // for basic_ostream::ope...
+#include <stdexcept>                              // for runtime_error
+#include <grpcpp/impl/codegen/client_context.h>   // for ClientContext
+#include <grpcpp/impl/codegen/status.h>           // for Status
+#include "grpcpp/impl/codegen/status_code_enum.h" // for StatusCode, UNAUTH...
+#include "caosdb/entity/v1alpha1/main.pb.h"
+#include "caosdb/entity/v1alpha1/main.grpc.pb.h"
+
+namespace caosdb::transaction {
+using caosdb::entity::v1alpha1::EntityTransactionService;
+using caosdb::entity::v1alpha1::RetrieveRequest;
+using caosdb::entity::v1alpha1::RetrieveResponse;
+using caosdb::exceptions::AuthenticationError;
+using caosdb::exceptions::ConnectionError;
+
+Transaction::Transaction(
+  std::shared_ptr<EntityTransactionService::Stub> service_stub) {
+  this->service_stub = std::move(service_stub);
+}
+
+auto Transaction::Retrieve(const EntityID &id) -> void {
+
+  RetrieveRequest request;
+  request.mutable_entity()->set_eid(id.ToString());
+
+  this->request = request;
+}
+
+auto Transaction::Execute() -> void {
+  // TODO(tf) throw error if not int INIT state
+  this->state = TransactionState::EXECUTING;
+
+  RetrieveResponse response;
+  grpc::ClientContext context;
+  const grpc::Status status =
+    this->service_stub->Retrieve(&context, this->request, &response);
+
+  if (!status.ok()) {
+    switch (status.error_code()) {
+    case grpc::StatusCode::UNAUTHENTICATED:
+      throw AuthenticationError(status.error_message());
+    case grpc::StatusCode::UNAVAILABLE:
+      throw ConnectionError(status.error_message());
+    default:
+      std::cout << status.error_code() << "\n";
+      throw std::runtime_error(status.error_message());
+    }
+  }
+  // TODO(tf)
+  this->result_set = std::make_shared<ResultSet>();
+}
+
+auto Transaction::WaitForIt() const -> void {
+  // TODO(tf) throw error if still in INIT state
+  // TODO(tf)
+}
+
+[[nodiscard]] auto Transaction::GetResultSet() const
+  -> std::shared_ptr<ResultSet> {
+  return this->result_set;
+}
+
+} // namespace caosdb::transaction
diff --git a/src/caosdbcli.cpp b/src/caosdbcli.cpp
index 7dc2d718317d6d9be2f2d1166bcaa658c78b6ecf..3151919c569dc3ad648317539bc07bc5c4adce79 100644
--- a/src/caosdbcli.cpp
+++ b/src/caosdbcli.cpp
@@ -57,10 +57,10 @@ auto main() -> int {
   auto cacert =
     std::make_shared<caosdb::connection::PemFileCACertProvider>(pem_file);
   auto config = std::make_shared<caosdb::connection::SslCaosDBConnectionConfig>(
-    host, port, cacert);
+    host, port, cacert, auth);
   caosdb::connection::CaosDBConnection connection(config);
   std::cout << std::endl << connection << std::endl;
-  const auto &v_info = connection.getVersionInfo();
+  const auto &v_info = connection.GetVersionInfo();
   std::cout << "VersionInfo(" << v_info->GetMajor() << "." << v_info->GetMinor()
             << "." << v_info->GetPatch() << "-" << v_info->GetPreRelease()
             << "-" << v_info->GetBuild() << ")" << std::endl;
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index d1e1c4df28c3f0398a26c1d87f5a2be821da2deb..78e25bec0a671406d3705f8fe1c6ca5846741e7c 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -21,8 +21,9 @@
 # append all the test cases here (file name without the ".cpp" suffix)
 set(test_cases
     test_connection
-    test_utils
     test_info
+    test_transaction
+    test_utils
     )
 
 
@@ -46,8 +47,8 @@ foreach (i RANGE "${len_test_cases}")
     add_executable(${test_case_name} ${test_case_name}.cpp)
     set(libcaosdb_TEST_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${test_case_name}.cpp
         ${libcaosdb_TEST_SRC}")
-    target_link_libraries(${test_case_name} PRIVATE caosdb
-        caosdb_info_v1alpha1 ${CONAN_LIBS_GTEST} ${CONAN_LIBS_BOOST})
+    target_link_libraries(${test_case_name}
+        PRIVATE ${LIBCAOSDB} ${CONAN_LIBS_GTEST} ${CONAN_LIBS_BOOST})
     if(_LINTING)
     set_target_properties(${test_case_name}
         PROPERTIES
diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cb13a3f2ee2b80656b64768765a2a3cb2d93490d
--- /dev/null
+++ b/test/test_transaction.cpp
@@ -0,0 +1,43 @@
+/*
+ *
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <memory>                  // for allocator, make_shared, unique_ptr
+#include "caosdb/connection.h"     // for InsecureCaosDBConnectionConfig
+#include "caosdb/entity.h"         // for EntityID
+#include "caosdb/transaction.h"    // for Transaction, EntityID
+#include "gtest/gtest_pred_impl.h" // for Test, SuiteApiResolver, TEST, Tes...
+
+namespace caosdb::transaction {
+using caosdb::connection::CaosDBConnection;
+using caosdb::connection::InsecureCaosDBConnectionConfig;
+
+TEST(test_transaction, create_transaction) {
+  const auto *pHost = "localhost";
+  auto config = std::make_shared<InsecureCaosDBConnectionConfig>(pHost, 8000);
+  CaosDBConnection connection(config);
+  auto transaction = connection.CreateTransaction();
+
+  transaction->Retrieve(EntityID("someid"));
+  transaction->Execute();
+}
+
+} // namespace caosdb::transaction