diff --git a/CMakeLists.txt b/CMakeLists.txt
index 91c0423ade5bd19d649237dc0a4a14eec0301300..bb4ddc34b8553f9fd9f78d7707ac4706d56f2214 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,7 +20,7 @@
 
 cmake_minimum_required(VERSION 3.14)
 
-set(libcaosdb_VERSION 0.0.5)
+set(libcaosdb_VERSION 0.0.6)
 set(libcaosdb_COMPATIBLE_SERVER_VERSION_MAJOR 0)
 set(libcaosdb_COMPATIBLE_SERVER_VERSION_MINOR 5)
 set(libcaosdb_COMPATIBLE_SERVER_VERSION_PATCH 0)
diff --git a/conanfile.py b/conanfile.py
index 1ed1abd041788b27a8a63c7286528fbed7cf4740..4ae59d5c79392e6cacc26e6dbfe2306010be3c0e 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.5"
+    version = "0.0.6"
     license = "AGPL-3.0-or-later"
     author = "Timm C. Fitschen <t.fitschen@indiscale.com>"
     url = "https://gitlab.indiscale.com/caosdb/src/caosdb-cpplib.git"
diff --git a/include/caosdb/connection.h b/include/caosdb/connection.h
index 2c58d57ce66375d1650834872d461464dd912be0..abe53fb04d28256be9a6c7ebec149f7eb5196b40 100644
--- a/include/caosdb/connection.h
+++ b/include/caosdb/connection.h
@@ -45,6 +45,7 @@ using caosdb::entity::v1alpha1::EntityTransactionService;
 using caosdb::info::VersionInfo;
 using caosdb::info::v1alpha1::GeneralInfoService;
 using caosdb::transaction::Transaction;
+using caosdb::transaction::TransactionStatus;
 using grpc::ChannelCredentials;
 
 class CertificateProvider {
@@ -82,11 +83,7 @@ private:
 public:
   ConnectionConfiguration(const std::string &host, int port);
   virtual ~ConnectionConfiguration() = default;
-  friend auto operator<<(std::ostream &out,
-                         const ConnectionConfiguration &configuration)
-    -> std::ostream &;
 
-  [[nodiscard]] auto virtual ToString() const -> std::string = 0;
   [[nodiscard]] auto GetHost() const -> std::string;
   [[nodiscard]] auto GetPort() const -> int;
   [[nodiscard]] auto virtual GetChannelCredentials() const
@@ -101,7 +98,6 @@ public:
   InsecureConnectionConfiguration(const std::string &host, int port);
   [[nodiscard]] auto GetChannelCredentials() const
     -> std::shared_ptr<ChannelCredentials> override;
-  [[nodiscard]] auto ToString() const -> std::string override;
 };
 
 class TlsConnectionConfiguration : public ConnectionConfiguration {
@@ -120,23 +116,62 @@ public:
                              const Authenticator &authenticator);
   [[nodiscard]] auto GetChannelCredentials() const
     -> std::shared_ptr<ChannelCredentials> override;
-  [[nodiscard]] auto ToString() const -> std::string override;
 };
 
 /**
  * @brief A reusable connection to a CaosDBServer.
  */
 class Connection {
-  std::shared_ptr<grpc::Channel> channel;
-  std::unique_ptr<GeneralInfoService::Stub> general_info_service;
-  std::shared_ptr<EntityTransactionService::Stub> entity_transaction_service;
-
 public:
   explicit Connection(const ConnectionConfiguration &configuration);
-  friend auto operator<<(std::ostream &out, const Connection &connection)
-    -> std::ostream &;
-  [[nodiscard]] auto GetVersionInfo() const -> std::unique_ptr<VersionInfo>;
+
+  /**
+   * Request the server's version and return the status of this request after
+   * termination..
+   *
+   * The version is stored in the connection object and may be retrieved via
+   * GetVersionInfo() if the request was successful.
+   *
+   * This method does not throw any exceptions. Errors are indicated in the
+   * return value instead.
+   */
+  auto RetrieveVersionInfoNoExceptions() const noexcept -> TransactionStatus;
+
+  /**
+   * Request and return the server's version.
+   *
+   * If the request terminated unsuccessfully, a corresponding exception is
+   * being thrown.
+   */
+  auto RetrieveVersionInfo() const -> const VersionInfo &;
+
+  /**
+   * Return the server's version.
+   *
+   * Clients need to call RetrieveVersionInfo() or
+   * RetrieveVersionInfoNoExceptions() before the version info is locally
+   * available. Otherwise an empty instance is being returned.
+   */
+  [[nodiscard]] inline auto GetVersionInfo() const noexcept
+    -> const VersionInfo & {
+    return this->version_info;
+  };
+
   [[nodiscard]] auto CreateTransaction() const -> std::unique_ptr<Transaction>;
+
+private:
+  /// GRPC-Channel (HTTP/2 Connection plus Authentication). We use a shared
+  /// pointer because Transaction instances also own the channel.
+  std::shared_ptr<grpc::Channel> channel;
+  /// Service for retrieving the server's version. We use a unique pointer
+  /// because only this connection owns and uses this service.
+  std::unique_ptr<GeneralInfoService::Stub> general_info_service;
+  /// The server's version. It's mutable because it is rather a cache than a
+  /// data member which is subject to change.
+  mutable VersionInfo version_info;
+  /// Service for entity transactions. We use a shared pointer because
+  /// Transaction instances also own this service stub.
+  std::shared_ptr<EntityTransactionService::Stub> entity_transaction_service;
 };
 
 /**
diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h
index 8989951997a9da6bb821dec9e55345bbbb0c72b9..0f9749534b57e8137582397603e5b5a6680339be 100644
--- a/include/caosdb/transaction.h
+++ b/include/caosdb/transaction.h
@@ -26,8 +26,9 @@
 #ifndef CAOSDB_TRANSACTION_H
 #define CAOSDB_TRANSACTION_H
 
-#include <memory>                                // for shared_ptr, unique_ptr
-#include <string>                                // for string
+#include <memory> // for shared_ptr, unique_ptr
+#include <string> // for string
+#include "caosdb/localization.h"
 #include "caosdb/entity.h"                       // for Entity
 #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe...
 #include "caosdb/entity/v1alpha1/main.pb.h"      // for Entity, RetrieveReq...
@@ -54,7 +55,146 @@ private:
   std::unique_ptr<Entity> entity;
 };
 
-enum TransactionState { INIT = 10, EXECUTING = 20, SUCCESS = 30, ERROR = 40 };
+enum StatusCode {
+  INITIAL = -2,
+  EXECUTING = -1,
+  SUCCESS = 0,
+  UNAUTHENTICATED = 16,
+  UNAVAILABLE = 14,
+  GENERIC_RPC_ERROR = 20,
+  GENERIC_TRANSACTION_ERROR = 21,
+};
+
+/**
+ * State of a transaction instance.
+ */
+class TransactionStatus {
+public:
+  inline static const auto INITIAL = TransactionStatus(
+    0, StatusCode::INITIAL,
+    _("The transaction has not been executed yet and clients can still "
+      "change it."));
+  static const auto EXECUTING =
+    TransactionStatus(10, StatusCode::EXECUTING,
+                      _("The transaction is currently being executed."));
+  static const auto SUCCESS = TransactionStatus(
+    20, StatusCode::SUCCESS,
+    _("The transaction has been executed and it terminated successfully."));
+  static const auto
+    RPC_ERROR = TransactionStatus(
+      100, StatusCode::GENERIC_RPC_ERROR,
+      _("The attempt to execute this transaction was not successful because an "
+        "error occured in the transport or RPC protocol layer.")),
+    static const auto CONNECTION_ERROR = TransactionStatus(
+      200, StatusCode::UNAVAILABLE,
+      _("The attempt to execute this transaction was not successful because "
+        "the connection to the server could not be established."));
+  static const auto AUTHENTICATION_ERROR = TransactionStatus(
+    300, StatusCode::UNAUTHENTICATED,
+    _("The attempt to execute this transaction has not been executed at all "
+      "because the authentication did not succeed."));
+  static const auto TRANSACTION_ERROR = TransactionStatus(
+    1000, StatusCode::GENERIC_TRANSACTION_ERROR,
+    _("The transaction terminated unsuccessfully with transaction errors."));
+
+  /**
+   * Return true if this TransactionStatus represents a terminated state.
+   */
+  inline auto IsTerminated() const -> bool {
+    return this->numeric_value >= 20;
+  };
+
+  /**
+   * Return true if this TransactionStatus represents an erroneous state.
+   */
+  inline auto IsError() const -> bool { return this->numeric_value >= 100; };
+
+  /**
+   * Equals operator is overloaded so we can use this class is if it were an
+   * Enum.
+   */
+  inline int operator==(const TransactionStatus &other) {
+    return this->numeric_value == other.numeric_value;
+  };
+
+  /**
+   * Instantiate and return a new instance with the given code and description.
+   *
+   * Instances with a code and a description may be used to describe an
+   * erroneous state in more detail.
+   *
+   * This method should not be called on non-error states.
+   */
+  inline auto Instantiate(StatusCode code, const std::string &details) const
+    -> const TransactionStatus {
+    return TransactionStatus(this->numeric_value, code,
+                             this->description + " " + detail);
+  };
+
+  /**
+   * Instantiate and return a new instance with the given code.
+   *
+   * Instances with a code may be used to describe an erroneous state in more
+   * detail.
+   *
+   * This method should not be called on non-error states.
+   */
+  inline auto Instantiate(StatusCode code) const -> const TransactionStatus {
+    return TransactionStatus(this->numeric_value, code, this->description);
+  }
+
+  /**
+   * Instantiate and return a new instance with the given code and description.
+   *
+   * Instances descriptions may be used to describe an erroneous state in more
+   * detail.
+   *
+   * This method should no be called on non-error states.
+   */
+  inline auto Instantiate(const std::string &details) const
+    -> const TransactionStatus {
+    return TransactionStatus(this->numeric_value, this->code,
+                             this->description + " " + details);
+  }
+
+  /**
+   * Return a description of the erroneous state.
+   *
+   * Returns an empty string if there is no description.
+   */
+  inline auto GetDescription() const -> const std::string & {
+    return this->description;
+  }
+
+  /**
+   * Return the status code of the state.
+   */
+  inline auto GetCode() const -> StatusCode { return this->code; }
+
+private:
+  /**
+   * The numeric_value is for class-internal use only.
+   *
+   * We use it to group errors with different error codes together, e.g. all
+   * transaction errors.
+   */
+  const int numeric_value;
+
+  /**
+   * The code is an identifier of errors and should not be confused with the
+   * class-internal numeric_value.
+   */
+  const StatusCode code;
+
+  /**
+   * Description of the error
+   */
+  const std::string description;
+
+  TransactionStatus(int numeric_value, StatusCode code,
+                    const std::string &description)
+    : numeric_value(numeric_value), code(code), description(description){};
+};
 
 /**
  * @brief Create a transaction via `CaosDBConnection.createTransaction()`
@@ -62,18 +202,50 @@ enum TransactionState { INIT = 10, EXECUTING = 20, SUCCESS = 30, ERROR = 40 };
 class Transaction {
 private:
   std::unique_ptr<ResultSet> result_set;
-  TransactionState state = TransactionState::INIT;
+  TransactionStatus state = TransactionStatus::INITIAL;
   std::shared_ptr<EntityTransactionService::Stub> service_stub;
   RetrieveRequest request; // TODO(tf)
+  std::string error_message;
 
 public:
   Transaction(std::shared_ptr<EntityTransactionService::Stub> service_stub);
   auto RetrieveById(const std::string &id) -> void;
-  auto Execute() -> void;
+
+  /**
+   * Execute this transaction in blocking mode and return the state.
+   */
+  auto Execute() -> TransactionStatus;
+
+  /**
+   * Execute this transaction in non-blocking mode and return immediately.
+   *
+   * A client may request the current state at any time via GetState().
+   *
+   * Use WaitForIt() to join the back-ground execution of this transaction.
+   */
+  auto ExecuteAsynchronously() -> void;
+
+  /**
+   * Join the background execution and return the state when the execution
+   * terminates.
+   *
+   * Use this after ExecuteAsynchronously().
+   */
+  auto WaitForIt() -> TransactionStatus;
+
+  /**
+   * Return the current state of the transaction.
+   */
+  [[nodiscard]] inline auto GetState() const -> TransactionStatus {
+    return this->state;
+  }
+
   [[nodiscard]] inline auto GetResultSet() const -> const ResultSet & {
     const ResultSet *result_set = this->result_set.get();
     return *result_set;
   }
+
+  auto GetErrors
 };
 
 } // namespace caosdb::transaction
diff --git a/proto b/proto
index 12f072263c05208464b80c0124bde0396b100d86..3f867687cc71e42e964051b35f856abf6b1c8f09 160000
--- a/proto
+++ b/proto
@@ -1 +1 @@
-Subproject commit 12f072263c05208464b80c0124bde0396b100d86
+Subproject commit 3f867687cc71e42e964051b35f856abf6b1c8f09
diff --git a/src/caosdb/connection.cpp b/src/caosdb/connection.cpp
index d2a2ae1fb2408362c18c5a7d5815ac75e3c91406..483b3656fffc7f0a260df692f7610d1a8944ff31 100644
--- a/src/caosdb/connection.cpp
+++ b/src/caosdb/connection.cpp
@@ -84,12 +84,6 @@ auto ConnectionConfiguration::GetHost() const -> std::string {
 
 auto ConnectionConfiguration::GetPort() const -> int { return this->port; }
 
-auto operator<<(std::ostream &out, const ConnectionConfiguration &configuration)
-  -> std::ostream & {
-  out << configuration.ToString();
-  return out;
-}
-
 InsecureConnectionConfiguration::InsecureConnectionConfiguration(
   const std::string &host, int port)
   : ConnectionConfiguration(host, port) {
@@ -101,11 +95,6 @@ auto InsecureConnectionConfiguration::GetChannelCredentials() const
   return this->credentials;
 }
 
-auto InsecureConnectionConfiguration::ToString() const -> std::string {
-  return "InsecureConnectionConfiguration(" + this->GetHost() + "," +
-         std::to_string(this->GetPort()) + ")";
-}
-
 TlsConnectionConfiguration::TlsConnectionConfiguration(const std::string &host,
                                                        int port)
   : ConnectionConfiguration(host, port) {
@@ -148,12 +137,6 @@ auto TlsConnectionConfiguration::GetChannelCredentials() const
   return this->credentials;
 }
 
-auto TlsConnectionConfiguration::ToString() const -> std::string {
-  return "TlsConnectionConfiguration(" + this->GetHost() + "," +
-         std::to_string(this->GetPort()) + "," + this->certificate_provider +
-         ")";
-}
-
 Connection::Connection(const ConnectionConfiguration &configuration) {
   const std::string target =
     configuration.GetHost() + ":" + std::to_string(configuration.GetPort());
@@ -164,33 +147,38 @@ Connection::Connection(const ConnectionConfiguration &configuration) {
     std::make_shared<EntityTransactionService::Stub>(this->channel);
 }
 
-auto operator<<(std::ostream &out, const Connection & /*connection*/)
-  -> std::ostream & {
-  out << "Connection()";
-  return out;
-}
+auto Connection::RetrieveVersionInfoNoExceptions() const noexcept
+  -> TransactionStatus {
 
-[[nodiscard]] auto Connection::GetVersionInfo() const
-  -> std::unique_ptr<VersionInfo> {
   const GetVersionInfoRequest request;
   GetVersionInfoResponse response;
   grpc::ClientContext context;
   const grpc::Status status =
     this->general_info_service->GetVersionInfo(&context, request, &response);
 
+  TransactionStatus &status = TransactionStatus::SUCCESS;
   if (!status.ok()) {
     switch (status.error_code()) {
     case grpc::StatusCode::UNAUTHENTICATED:
-      throw AuthenticationError(status.error_message());
+      status = TransactionStatus::AUTHENTICATION_ERROR;
     case grpc::StatusCode::UNAVAILABLE:
-      throw ConnectionError(status.error_message());
+      status = TransactionStatus::CONNECTION_ERROR;
     default:
-      throw std::runtime_error("Status Code " +
-                               std::to_string(status.error_code()) + " - " +
-                               status.error_message());
+      status = TransactionStatus::RPC_ERROR.Instantiate(
+        std::to_string(status.error_code()) + " - " _(status.error_message()));
     }
   }
-  return std::make_unique<VersionInfo>(response.release_version_info());
+
+  return status;
+}
+
+auto Connection::RetrieveVersionInfo() const -> const VersionInfo & {
+
+  TransactionStatus status = RetrieveVersionInfoNoExceptions();
+  if (status.IsError()) {
+    throw status.CreateException();
+  }
+  return GetVersionInfo();
 }
 
 [[nodiscard]] auto Connection::CreateTransaction() const
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
index bf2e88411fa15fd94f7b1ca4a1546c1bc23e2b90..317d20518341e662442d0d84be63d2f9f184c031 100644
--- a/src/caosdb/transaction.cpp
+++ b/src/caosdb/transaction.cpp
@@ -57,9 +57,9 @@ auto Transaction::RetrieveById(const std::string &id) -> void {
   this->request = request;
 }
 
-auto Transaction::Execute() -> void {
+auto Transaction::Execute() -> TransactionStatus {
   // TODO(tf) throw error if not int INIT state
-  this->state = TransactionState::EXECUTING;
+  this->state = TransactionStatus::EXECUTING;
 
   RetrieveResponse response;
   grpc::ClientContext context;
@@ -67,20 +67,31 @@ auto Transaction::Execute() -> void {
     this->service_stub->Retrieve(&context, this->request, &response);
 
   if (!status.ok()) {
+    // TODO
     switch (status.error_code()) {
     case grpc::StatusCode::UNAUTHENTICATED:
-      throw AuthenticationError(status.error_message());
+      this->state = TransactionStatus::ERROR.Instantiate(
+        StatusCode::UNAUTHENTICATED, status.error_message());
     case grpc::StatusCode::UNAVAILABLE:
-      throw ConnectionError(status.error_message());
+      this->state = TransactionStatus::ERROR.Instantiate(
+        StatusCode::UNAVAILABLE, status.error_message());
     default:
-      std::cout << status.error_code() << "\n";
-      throw std::runtime_error(status.error_message());
+      this->state = TransactionStatus::ERROR.Instantiate(
+        StatusCode::GENERIC_RPC_ERROR, status.error_message());
     }
+  } else {
+    this->state = TransactionStatus::SUCCESS;
   }
 
   auto *entity = response.release_entity();
   auto result_set = std::make_unique<UniqueResult>(entity);
   this->result_set = std::move(result_set);
+
+  return this->state;
 }
 
+auto Transaction::ExecuteAsynchronously() -> TransactionStatus {}
+
+auto Transaction::WaitForIt() -> TransactionStatus {}
+
 } // namespace caosdb::transaction