diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b45dde5de1e4cafdbc02a15c4b775b763d5447bd..9f8c131968c050fb18001b5dc7c5468d0ed26dae 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -22,7 +22,7 @@
 
 variables:
   DEPLOY_REF: dev
-  CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/caosdb-pylib/testenv:latest
+  CI_REGISTRY_IMAGE: $CI_REGISTRY/caosdb/src/caosdb-pylib/testenv:latest
   # When using dind, it's wise to use the overlayfs driver for
   # improved performance.
 
@@ -65,7 +65,7 @@ trigger_build:
   stage: deploy
   script:
     - /usr/bin/curl -X POST
-      -F token=$DEPLOY_TRIGGER_TOKEN
+      -F token=$CI_JOB_TOKEN
       -F "variables[F_BRANCH]=$CI_COMMIT_REF_NAME"
       -F "variables[PYLIB]=$CI_COMMIT_REF_NAME"
       -F "variables[TriggerdBy]=PYLIB"
@@ -92,15 +92,13 @@ build-testenv:
     - docker push $CI_REGISTRY_IMAGE
 
 # Build the sphinx documentation and make it ready for deployment by Gitlab Pages
-# documentation:
-#   stage: deploy
-
 # Special job for serving a static website. See https://docs.gitlab.com/ee/ci/yaml/README.html#pages
-pages:
+pages_prepare: &pages_prepare
+  tags: [ cached-dind ]
   stage: deploy
   only:
-      # TODO this should be for master only, once releases are more regularly
-    - dev
+    refs:
+      - /^release-.*$/i
   script:
     - echo "Deploying"
     - make doc
@@ -108,3 +106,8 @@ pages:
   artifacts:
     paths:
       - public
+pages:
+  <<: *pages_prepare
+  only:
+    refs:
+      - main
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e190313f02f94d31bb3a0e9f0310eed7b729ea4..38b470db9d36675ffcef0b7e1434a08c3be7f407 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,24 +17,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed ###
 
+* #53 Documentation of inheritance
+
 ### Security ###
 
-## [0.5.1] - 2021-02-12 ##
+## [0.5.2] - 2021-06-03 ##
 
 ### Added ###
 
+* Entity State support (experimental, no StateModel support yet). See the
+  `caosdb.State` class for more information.
+* `etag` property for the `caosdb.Query` class. The etag allows to debug the
+  caching and to decide whether the server has changed between queries.
+* function `_read_config_files` to read `pycaosdb.ini` files from different paths.
+
 ### Changed ###
 
+* Updated error-handling tutorial in documentation to reflect the new
+  error classes
+
 ### Deprecated ###
 
 ### Removed ###
 
 ### Fixed ###
-
-* #43 - Error with `execute_query` when server doesn't support query caching.
+* #45 - test_config_ini_via_envvar
 
 ### Security ###
 
+## [0.5.1] - 2021-02-12 ##
+
+### Fixed ###
+
+* #43 - Error with `execute_query` when server doesn't support query caching.
+
 ## [0.5.0] - 2021-02-11 ##
 
 ### Added ###
@@ -67,19 +83,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   `QueryNotUniqueError` if no or more than one possible candidate is
   found, respectively.
 
-### Deprecated ###
-
 ### Removed ###
 
 * Dynamic exception type `EntityMultiError`. 
 * `get_something` functions from all error object in `exceptions.py`
 * `AmbiguityException`
 
-
-### Fixed ###
-
-### Security ###
-
 ## [0.4.1] - 2021-02-10 ##
 
 ### Added ###
@@ -89,18 +98,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   entity.
 * Automated documentation builds: `make doc`
 
-### Changed ###
-
-### Deprecated ###
-
-### Removed ###
-
 ### Fixed ###
 
 * deepcopy of `_Messages` objects
 
-### Security ###
-
 ## [0.4.0] - 2020-07-17##
 
 ### Added ###
diff --git a/README.md b/README.md
index e44702a3a22c28af986d641b5d7e454f915326c8..04b34cbc07c98e73740b13200ed83fe067af99d2 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,49 @@
-# Welcome
+
+# README
+
+## Welcome
 
 This is the **CaosDB Python Client Library** repository and a part of the
 CaosDB project.
 
-# Setup
+## Setup
 
 Please read the [README_SETUP.md](README_SETUP.md) for instructions on how to
 setup this code.
 
 
-# Further Reading
+## Further Reading
+
+Please refer to the [official documentation](https://docs.indiscale.com/caosdb-pylib/) for more information.
+
+## Contributing
+
+Thank you very much to all contributers—[past, present](https://gitlab.com/caosdb/caosdb/-/blob/dev/HUMANS.md), and prospective ones.
 
-Please refer to the [official gitlab repository of the CaosDB
-project](https://gitlab.com/caosdb/caosdb) for more information.
+### Code of Conduct
 
-# License
+By participating, you are expected to uphold our [Code of Conduct](https://gitlab.com/caosdb/caosdb/-/blob/dev/CODE_OF_CONDUCT.md).
+
+### How to Contribute
+
+* You found a bug, have a question, or want to request a feature? Please 
+[create an issue](https://gitlab.com/caosdb/caosdb-pylib/-/issues).
+* You want to contribute code?
+    * **Forking:** Please fork the repository and create a merge request in GitLab and choose this repository as
+      target. Make sure to select "Allow commits from members who can merge the target branch" under
+      Contribution when creating the merge request. This allows our team to work with you on your
+      request.
+    * **Code style:** This project adhers to the PEP8 recommendations, you can test your code style
+      using the `autopep8` tool (`autopep8 -i -r ./`).  Please write your doc strings following the
+      [NumpyDoc](https://numpydoc.readthedocs.io/en/latest/format.html) conventions.
+* If you have a suggestion for the [documentation](https://docs.indiscale.com/caosdb-pylib/),
+the preferred way is also a merge request as describe above (the documentation resides in `src/doc`).
+However, you can also create an issue for it. 
+* You can also contact us at **info (AT) caosdb.de** and join the
+  CaosDB community on
+  [#caosdb:matrix.org](https://matrix.to/#/!unwwlTfOznjEnMMXxf:matrix.org).
+
+## License
 
 * Copyright (C) 2018 Research Group Biomedical Physics, Max Planck Institute
   for Dynamics and Self-Organization Göttingen.
@@ -22,4 +51,3 @@ project](https://gitlab.com/caosdb/caosdb) for more information.
 
 All files in this repository are licensed under a [GNU Affero General Public
 License](LICENCE.md) (version 3 or later).
-
diff --git a/README_SETUP.md b/README_SETUP.md
index 2db73cfaec2f6aadfc7fa3742892d970d562c946..9da548395073643c16539cef180c4d6412dd8d46 100644
--- a/README_SETUP.md
+++ b/README_SETUP.md
@@ -75,7 +75,7 @@ current working directory will be read additionally, if it exists.
 
 Here, we will look at the most common configuration options. For a full and 
 comprehensive description please check out 
-[pycaosdb.ini file](https://gitlab.com/caosdb/caosdb-pylib/-/blob/master/examples/pycaosdb.ini) 
+[pycaosdb.ini file](https://gitlab.com/caosdb/caosdb-pylib/-/blob/main/examples/pycaosdb.ini) 
 You can download this file and use it as a starting point.
 
 
@@ -84,7 +84,7 @@ Typically, you need to change at least the `url` and `username` fields as requir
 you do not know what to put there, but for the demo instances https://demo.indiscale.com, `username=admin`
 and `password=caosdb` should work).
 
-### Authentication ##
+### Authentication ###
 
 The default configuration (that your are asked for your password when ever a connection is created
 can be changed by setting `password_method`:
@@ -109,7 +109,7 @@ The following illustrates the recommended options:
 #password_method=keyring
 ```
 
-### SSL Certificate ##
+### SSL Certificate ###
 In some cases (especially if you are testing CaosDB) you might need to supply 
 an SSL certificate to allow SSL encryption.
 
@@ -118,9 +118,9 @@ an SSL certificate to allow SSL encryption.
 cacert=/path/to/caosdb.ca.pem
 ```
 
-### Further Settings ##
+### Further Settings ###
 As mentioned above, a complete list of options can be found in the 
-[pycaosdb.ini file](https://gitlab.com/caosdb/caosdb-pylib/-/blob/master/examples/pycaosdb.ini) in 
+[pycaosdb.ini file](https://gitlab.com/caosdb/caosdb-pylib/-/blob/main/examples/pycaosdb.ini) in 
 the examples folder of the source code.
 
 ## Try it out ##
@@ -138,21 +138,20 @@ Out[2]: Connection to CaosDB with 501 Records.
 Note: This setup will ask you for your password whenever a new connection is created. If you do not
 like this, check out the "Authentication" section in the [configuration documentation](configuration.md).
 
-Now would be a good time to continue with the [tutorials](tutorials.html).
+Now would be a good time to continue with the [tutorials](tutorials/index).
 
 ## Run Unit Tests
 tox
 
-## Code Formatting
-
-autopep8 -i -r ./
-
-## Documentation #
+## Documentation ##
 
 Build documentation in `build/` with `make doc`.
 
-### Requirements ##
+### Requirements ###
 
 - `sphinx`
 - `sphinx-autoapi`
 - `recommonmark`
+
+### Troubleshooting ###
+If the client is to be executed directly from the `/src` folder, an initial `.\setup.py install --user` must be called.
diff --git a/RELEASE_GUIDELINES.md b/RELEASE_GUIDELINES.md
index dd6d5af4c43e1eea17c70f29da837c905694fed4..e015b598117abdcd575cf17e2f095fec459a4c4c 100644
--- a/RELEASE_GUIDELINES.md
+++ b/RELEASE_GUIDELINES.md
@@ -24,9 +24,9 @@ guidelines of the CaosDB Project
    variables and set `ISRELEASED` to `True`. Use the possibility to issue
    pre-release versions for testing.
 
-5. Merge the release branch into the master branch.
+5. Merge the release branch into the main branch.
 
-6. Tag the latest commit of the master branch with `v<VERSION>`.
+6. Tag the latest commit of the main branch with `v<VERSION>`.
 
 7. Delete the release branch.
 
@@ -35,8 +35,8 @@ guidelines of the CaosDB Project
 9. Publish the release by executing `./release.sh` with uploads the caosdb
    module to the Python Package Index [pypi.org](https://pypi.org).
 
-10. Merge the master branch back into the dev branch.
+10. Merge the main branch back into the dev branch.
 
-11. After the merge of master to dev, start a new development version by
+11. After the merge of main to dev, start a new development version by
     setting `ISRELEASED` to `False` and by increasing at least the `MIRCO`
     version in [setup.py](./setup.py)
diff --git a/examples/set_permissions.py b/examples/set_permissions.py
index dfc0a1510823a36d963f5d868052abb17b3fe12d..8162b11bfefb41b1bcdbc74b8e314f99a61d1a4e 100755
--- a/examples/set_permissions.py
+++ b/examples/set_permissions.py
@@ -25,15 +25,13 @@
 
 As a result, only a specific user or group may access it.
 
-This script assumes that data similar to the demo server of IndiScale (at
-demo.indiscale.com) exists on the server specified in the pycaosdb.ini
-configuration.
+This script assumes that the user specified in the pycaosdb.ini
+configuration can create new entities.
 
 """
 
 import caosdb as db
 from caosdb import administration as admin
-import lxml
 
 
 def assert_user_and_role():
@@ -50,27 +48,27 @@ out : tuple
     """
     try:
         human_user = admin._retrieve_user("jane")
-        _activate_user("jane")
-    except db.ResourceNotFoundError:
+        admin._update_user(name="jane", status="ACTIVE")
+    except db.HTTPResourceNotFoundError:
         human_user = admin._insert_user(
             "jane", password="Human_Rememberable_Password_1234", status="ACTIVE")
 
     try:
         alien_user = admin._retrieve_user("xaxys")
-        _activate_user("xaxys")
-    except db.ResourceNotFoundError:
+        admin._update_user(name="xaxys", status="ACTIVE")
+    except db.HTTPResourceNotFoundError:
         alien_user = admin._insert_user("xaxys", password="4321_Syxax",
                                         status="ACTIVE")
 
     # At the moment, the return value is only "ok" for successful insertions.
     try:
         human_role = admin._retrieve_role("human")
-    except db.ResourceNotFoundError:
+    except db.HTTPResourceNotFoundError:
         human_role = admin._insert_role("human", "An Earthling.")
 
     try:
         alien_role = admin._retrieve_role("alien")
-    except db.ResourceNotFoundError:
+    except db.HTTPResourceNotFoundError:
         alien_role = admin._insert_role("alien", "An Extra-terrestrial.")
 
     admin._set_roles("jane", ["human"])
@@ -80,24 +78,6 @@ out : tuple
             ("xaxys", list(admin._get_roles("xaxys"))))
 
 
-def _activate_user(user):
-    """Set the user state to "ACTIVE" if necessary.
-
-Parameters
-----------
-user : str
-    The user to activate.
-
-Returns
--------
-None
-
-    """
-    user_xml = lxml.etree.fromstring(admin._retrieve_user(user))
-    if user_xml.xpath("User")[0].attrib["status"] != "ACTIVE":
-        admin._update_user(user, status="ACTIVE")
-
-
 def get_entities(count=1):
     """Retrieve one or more entities.
 
@@ -111,7 +91,7 @@ Returns
 out : Container
     A container of retrieved entities, the length is given by the parameter count.
     """
-    cont = db.execute_query("FIND RECORD Guitar", flags={
+    cont = db.execute_query("FIND RECORD 'Human Food'", flags={
                             "P": "0L{n}".format(n=count)})
     if len(cont) != count:
         raise db.CaosDBException(
@@ -221,17 +201,36 @@ None
         print("Retrieval of all entities was successfully denied.")
 
 
+def create_test_entities():
+    """Create some test entities.
+    After calling this function, there will be a RecordType "Human Food" with the corresponding Records
+    "Bread", "Tomatoes", and "Twinkies" inserted in the database.
+    """
+    rt = db.RecordType(
+        name="Human Food", description="Food that can be eaten only by humans").insert()
+    food = ("Bread", "Tomatoes", "Twinkies")
+
+    cont = db.Container()
+    for i in range(len(food)):
+        rec = db.Record(food[i])
+        rec.add_parent(name="Human Food")
+        cont.append(rec)
+
+    cont.insert()
+
+
 def main():
     """The main function of this script."""
 
-    db.connection.connection.get_connection()._login()
-
+    """Create some test entities"""
+    create_test_entities()
+    """Create new users"""
     human, alien = assert_user_and_role()
-
-    # public, private, undefined entities
+    """Load the newly created entities."""
     entities = get_entities(count=3)
-
+    """Set permission for the entities (only humans are allowed to eat human food)"""
     set_permission(human[1][0], alien[1][0], entities)
+    """Test the permissions"""
     test_permission((human[0], "Human_Rememberable_Password_1234"),
                     (alien[0], "4321_Syxax"), entities)
 
diff --git a/setup.py b/setup.py
index d491a1d340796b9b4701b307003c3e62e75d6001..e1d39458ea8d1b0b17ea12a82ebd7133b27b045a 100755
--- a/setup.py
+++ b/setup.py
@@ -47,7 +47,7 @@ from setuptools import find_packages, setup
 
 MAJOR = 0
 MINOR = 5
-MICRO = 2
+MICRO = 3
 PRE = ""  # e.g. rc0, alpha.1, 0.beta-23
 ISRELEASED = False
 
diff --git a/src/caosdb/__init__.py b/src/caosdb/__init__.py
index b86fcd638c7321c2e0464b603fb736e3adfbafe3..7e06885fe495c1e8c4ccc99b7d0c0f8ff8c34b5b 100644
--- a/src/caosdb/__init__.py
+++ b/src/caosdb/__init__.py
@@ -37,7 +37,8 @@ from os.path import expanduser, join
 import caosdb.apiutils
 from caosdb.common import administration
 from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER,
-                                    REFERENCE, TEXT, LIST)
+                                    LIST, REFERENCE, TEXT)
+from caosdb.common.state import State, Transition
 # Import of the basic  API classes:
 from caosdb.common.models import (ACL, ALL, FIX, NONE, OBLIGATORY, RECOMMENDED,
                                   SUGGESTED, Container, DropOffBox, Entity,
@@ -45,15 +46,13 @@ from caosdb.common.models import (ACL, ALL, FIX, NONE, OBLIGATORY, RECOMMENDED,
                                   Query, QueryTemplate, Record, RecordType,
                                   delete, execute_query, get_global_acl,
                                   get_known_permissions, raise_errors)
-from caosdb.configuration import configure, get_config
+from caosdb.configuration import _read_config_files, configure, get_config
 from caosdb.connection.connection import configure_connection, get_connection
-from caosdb.version import version as __version__
 from caosdb.exceptions import *
+try:
+    from caosdb.version import version as __version__
+except ModuleNotFoundError:
+    version = "uninstalled"
+    __version__ = version
 
-# read configuration these files
-
-if "PYCAOSDBINI" in environ:
-    configure(expanduser(environ["PYCAOSDBINI"]))
-else:
-    configure(expanduser('~/.pycaosdb.ini'))
-configure(join(getcwd(), "pycaosdb.ini"))
+_read_config_files()
diff --git a/src/caosdb/common/administration.py b/src/caosdb/common/administration.py
index 7f9336c7473ac759aab4461c84b708a3b9049d8b..e7ba94182d7a4d8b60c6400cd1d804f62f7bf03c 100644
--- a/src/caosdb/common/administration.py
+++ b/src/caosdb/common/administration.py
@@ -32,7 +32,10 @@ from caosdb.common.utils import xml2str
 from caosdb.connection.connection import get_connection
 from caosdb.exceptions import (HTTPClientError,
                                HTTPForbiddenError,
-                               HTTPResourceNotFoundError)
+                               HTTPResourceNotFoundError,
+                               EntityDoesNotExistError,
+                               ServerConfigurationException,
+                               )
 
 
 def set_server_property(key, value):
@@ -53,9 +56,11 @@ def set_server_property(key, value):
     None
     """
     con = get_connection()
-
-    con._form_data_request(method="POST", path="_server_properties",
-                           params={key: value}).read()
+    try:
+        con._form_data_request(method="POST", path="_server_properties",
+                               params={key: value}).read()
+    except EntityDoesNotExistError:
+        raise ServerConfigurationException("Debug mode in server is probably disabled.") from None
 
 
 def get_server_properties():
@@ -69,7 +74,11 @@ def get_server_properties():
         The server properties.
     """
     con = get_connection()
-    body = con._http_request(method="GET", path="_server_properties").response
+    try:
+        body = con._http_request(method="GET", path="_server_properties").response
+    except EntityDoesNotExistError:
+        raise ServerConfigurationException("Debug mode in server is probably disabled.") from None
+
     xml = etree.parse(body)
     props = dict()
 
diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py
index 9592e484654d3a610d5516cb7ca52c3df71682ba..6ec49df2722170805fb6230753f36503870a8821 100644
--- a/src/caosdb/common/models.py
+++ b/src/caosdb/common/models.py
@@ -42,6 +42,7 @@ from warnings import warn
 
 from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, INTEGER, TEXT)
 from caosdb.common.versioning import Version
+from caosdb.common.state import State
 from caosdb.common.utils import uuid, xml2str
 from caosdb.configuration import get_config
 from caosdb.connection.connection import get_connection
@@ -112,6 +113,7 @@ class Entity(object):
         self.name = name
         self.description = description
         self.id = id
+        self.state = None
 
     @property
     def version(self):
@@ -429,14 +431,39 @@ class Entity(object):
     def add_parent(self, parent=None, **kwargs):  # @ReservedAssignment
         """Add a parent to this entity.
 
-        The first parameter is meant to identify the parent entity. So the method expects an instance of
-        Entity, an integer or a string here. Even though, by means of the **kwargs parameter you may pass
-        more parameters to this method. Accepted keywords are: id, name, inheritance. Any other keyword is
-        ignored right now but this may change in the future.
+        Parameters
+        ----------
+        parent : Entity or int or str or None
+            The parent entity, either specified by the Entity object
+            itself, or its id or its name. Default is None.
+        **kwargs : dict, optional
+            Additional keyword arguments for specifying the parent by
+            name or id, and for specifying the mode of inheritance.
+
+            id : int
+                Integer id of the parent entity. Ignored if `parent`
+                is not None.
+            name : str
+                Name of the parent entity. Ignored if `parent is not
+                none`.
+            inheritance : str
+                One of ``obligatory``, ``recommended``, ``suggested``, or ``fix``. Specifies the
+                minimum importance which parent properties need to have to be inherited by this
+                entity. If no `inheritance` is given, no properties will be inherited by the child.
+                This parameter is case-insensitive.
+
+                Note that the behaviour is currently not yet specified when assigning parents to
+                Records, it only works for inheritance of RecordTypes (and Properties).
+
+                For more information, it is recommended to look into the
+                :ref:`data insertion tutorial<tutorial-inheritance-properties>`.
+
+        Raises
+        ------
+        UserWarning
+            If neither a `parent` parameter, nor the `id`, nor `name`
+            parameter is passed to this method.
 
-        @param parent: An entity, an id or a name.
-        @param **kwargs: Accepted keywords: id, name, inheritance.
-        @raise UserWarning: If neither a 'parent' parameter, nor the 'id', nor 'name' parameter is passed to this method.
         """
         name = (kwargs['name'] if 'name' in kwargs else None)
         pid = (kwargs['id'] if 'id' in kwargs else None)
@@ -854,6 +881,8 @@ class Entity(object):
                     xml.append(v_elem)
             elif self.value == "":
                 xml.append(etree.Element("EmptyString"))
+            elif str(self.value) == "nan":
+                xml.text = "NaN"
             else:
                 xml.text = str(self.value)
 
@@ -907,6 +936,9 @@ class Entity(object):
         if self.acl is not None:
             xml.append(self.acl.to_xml())
 
+        if self.state is not None:
+            xml.append(self.state.to_xml())
+
         return xml
 
     @staticmethod
@@ -951,6 +983,8 @@ class Entity(object):
                 entity.add_message(child)
             elif isinstance(child, Version):
                 entity.version = child
+            elif isinstance(child, State):
+                entity.state = child
             elif child is None or hasattr(child, "encode"):
                 vals.append(child)
             elif isinstance(child, Entity):
@@ -1078,7 +1112,7 @@ class Entity(object):
             flags=flags)[0]
 
     def update(self, strict=False, raise_exception_on_error=True,
-               unique=True, flags=None):
+               unique=True, flags=None, sync=True):
         """Update this entity.
 
         There are two possible work-flows to perform this update:
@@ -1112,6 +1146,7 @@ class Entity(object):
 
         return Container().append(self).update(
             strict=strict,
+            sync=sync,
             raise_exception_on_error=raise_exception_on_error,
             unique=unique,
             flags=flags)[0]
@@ -1247,6 +1282,7 @@ class QueryTemplate():
         self.is_valid = lambda: False
         self.is_deleted = lambda: False
         self.version = None
+        self.state = None
 
     def retrieve(self, raise_exception_on_error=True, unique=True, sync=True,
                  flags=None):
@@ -1424,6 +1460,23 @@ class Property(Entity):
             property=property, value=value, **copy_kwargs)
 
     def add_parent(self, parent=None, **kwargs):
+        """Add a parent Entity to this Property.
+
+        Parameters
+        ----------
+        parent : Entity or int or str or None, optional
+            The parent entity
+        **kwargs : dict, optional
+            Additional keyword arguments specifying the parent Entity
+            by id or name, and specifying the inheritance level. See
+            :py:meth:`Entity.add_parent` for more information. Note
+            that by default, `inheritance` is set to ``fix``.
+
+        See Also
+        --------
+        Entity.add_parent
+
+        """
         copy_kwargs = kwargs.copy()
 
         if 'inheritance' not in copy_kwargs:
@@ -1502,6 +1555,24 @@ class RecordType(Entity):
                                     **copy_kwargs)
 
     def add_parent(self, parent=None, **kwargs):
+        """Add a parent to this RecordType
+
+        Parameters
+        ----------
+        parent : Entity or int or str or None, optional
+            The parent entity, either specified by the Entity object
+            itself, or its id or its name. Default is None.
+        **kwargs : dict, optional
+            Additional keyword arguments specifying the parent Entity by id or
+            name, and specifying the inheritance level. See
+            :py:meth:`Entity.add_parent` for more information. Note
+            that by default, `inheritance` is set to ``obligatory``.
+
+        See Also
+        --------
+        Entity.add_parent
+
+        """
         copy_kwargs = kwargs.copy()
 
         if 'inheritance' not in copy_kwargs:
@@ -2223,6 +2294,7 @@ def _basic_sync(e_local, e_remote):
     e_local.is_valid = e_remote.is_valid
     e_local.is_deleted = e_remote.is_deleted
     e_local.version = e_remote.version
+    e_local.state = e_remote.state
 
     if hasattr(e_remote, "query"):
         e_local.query = e_remote.query
@@ -2794,6 +2866,17 @@ class Container(list):
         this happens, none of them will be deleted. It occurs an error
         instead.
         """
+        chunk_size = 100
+        item_count = len(self)
+        # Split Container in 'chunk_size'-sized containers (if necessary) to avoid error 414 Request-URI Too Long
+        if item_count > chunk_size:
+            for i in range(0, int(item_count/chunk_size)+1):
+                chunk = Container()
+                for j in range(i*chunk_size, min(item_count, (i+1)*chunk_size)):
+                    chunk.append(self[j])
+                if len(chunk):
+                    chunk.delete()
+            return self
 
         if len(self) == 0:
             if raise_exception_on_error:
@@ -2840,7 +2923,7 @@ class Container(list):
         entity_url_segments = [_ENTITY_URI_SEGMENT, "&".join(id_str)]
 
         _log_request("DELETE: " + str(entity_url_segments) +
-                     ("?" + flags if flags is not None else ''))
+                     ("?" + str(flags) if flags is not None else ''))
 
         http_response = c.delete(entity_url_segments, query_dict=flags)
         cresp = Container._response_to_entities(http_response)
@@ -3419,6 +3502,22 @@ class ACL():
                         self.deny(username=username, realm=realm, role=role,
                                   permission=permission, priority=priority)
 
+    def combine(self, other):
+        """ Combine and return new instance."""
+        result = ACL()
+        result._grants.update(other._grants)
+        result._grants.update(self._grants)
+        result._denials.update(other._denials)
+        result._denials.update(self._denials)
+        result._priority_grants.update(other._priority_grants)
+        result._priority_grants.update(self._priority_grants)
+        result._priority_denials.update(other._priority_denials)
+        result._priority_denials.update(self._priority_denials)
+        return result
+
+    def __eq__(self, other):
+        return isinstance(other, ACL) and other._grants == self._grants and self._denials == other._denials and self._priority_grants == other._priority_grants and self._priority_denials == other._priority_denials
+
     def is_empty(self):
         return len(self._grants) + len(self._priority_grants) + \
             len(self._priority_denials) + len(self._denials) == 0
@@ -3661,6 +3760,7 @@ class Query():
         self.flags = dict()
         self.messages = _Messages()
         self.cached = None
+        self.etag = None
 
         if isinstance(q, etree._Element):
             self.q = q.get("string")
@@ -3670,6 +3770,7 @@ class Query():
                 self.cached = False
             else:
                 self.cached = q.get("cached").lower() == "true"
+            self.etag = q.get("etag")
 
             for m in q:
                 if m.tag.lower() == 'warning' or m.tag.lower() == 'error':
@@ -3714,6 +3815,7 @@ class Query():
         cresp = Container._response_to_entities(http_response)
         self.results = cresp.query.results
         self.cached = cresp.query.cached
+        self.etag = cresp.query.etag
 
         if self.q.lower().startswith('count') and len(cresp) == 0:
             # this was a count query
@@ -3942,6 +4044,8 @@ def _parse_single_xml_element(elem):
         return entity
     elif elem.tag.lower() == "version":
         return Version.from_xml(elem)
+    elif elem.tag.lower() == "state":
+        return State.from_xml(elem)
     elif elem.tag.lower() == "emptystring":
         return ""
     elif elem.tag.lower() == "value":
diff --git a/src/caosdb/common/state.py b/src/caosdb/common/state.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb74022bef57a77c8270b2033c904eecabaadf83
--- /dev/null
+++ b/src/caosdb/common/state.py
@@ -0,0 +1,198 @@
+# ** header v3.0
+# This file is a part of the CaosDB Project.
+#
+# Copyright (C) 2020 IndiScale GmbH <info@indiscale.com>
+# Copyright (C) 2020 Timm Fitschen <t.fitschen@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/>.
+#
+# ** end header
+
+import copy
+from lxml import etree
+
+
+def _translate_to_state_acis(acis):
+    result = set()
+    for aci in acis:
+        aci = copy.copy(aci)
+        if aci.role:
+            aci.role = "?STATE?" + aci.role + "?"
+        result.add(aci)
+    return result
+
+
+class Transition:
+    """Transition
+
+    Represents allowed transitions from one state to another.
+
+    Properties
+    ----------
+    name : str
+        The name of the transition
+    description: str
+        The description of the transition
+    from_state : str
+        A state name
+    to_state : str
+        A state name
+    """
+
+    def __init__(self, name, from_state, to_state, description=None):
+        self._name = name
+        self._from_state = from_state
+        self._to_state = to_state
+        self._description = description
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def description(self):
+        return self._description
+
+    @property
+    def from_state(self):
+        return self._from_state
+
+    @property
+    def to_state(self):
+        return self._to_state
+
+    def __repr__(self):
+        return f'Transition(name="{self.name}", from_state="{self.from_state}", to_state="{self.to_state}", description="{self.description}")'
+
+    def __eq__(self, other):
+        return (isinstance(other, Transition)
+                and other.name == self.name
+                and other.to_state == self.to_state
+                and other.from_state == self.from_state)
+
+    def __hash__(self):
+        return 23472 + hash(self.name) + hash(self.from_state) + hash(self.to_state)
+
+    @staticmethod
+    def from_xml(xml):
+        to_state = [to.get("name") for to in xml
+                    if to.tag.lower() == "tostate"]
+        from_state = [from_.get("name") for from_ in xml
+                      if from_.tag.lower() == "fromstate"]
+        result = Transition(name=xml.get("name"),
+                            description=xml.get("description"),
+                            from_state=from_state[0] if from_state else None,
+                            to_state=to_state[0] if to_state else None)
+        return result
+
+
+class State:
+    """State
+
+    Represents the state of an entity and take care of the serialization and
+    deserialization of xml for the entity state.
+
+    An entity state is always a State of a StateModel.
+
+    Properties
+    ----------
+    name : str
+        Name of the State
+    model : str
+        Name of the StateModel
+    description : str
+        Description of the State (read-only)
+    id : str
+        Id of the undelying State record (read-only)
+    transitions : set of Transition
+        All transitions which are available from this state (read-only)
+    """
+
+    def __init__(self, model, name):
+        self.name = name
+        self.model = model
+        self._id = None
+        self._description = None
+        self._transitions = None
+
+    @property
+    def id(self):
+        return self._id
+
+    @property
+    def description(self):
+        return self._description
+
+    @property
+    def transitions(self):
+        return self._transitions
+
+    def __eq__(self, other):
+        return (isinstance(other, State)
+                and self.name == other.name
+                and self.model == other.model)
+
+    def __hash__(self):
+        return hash(self.name) + hash(self.model)
+
+    def __repr__(self):
+        return f"State('{self.model}', '{self.name}')"
+
+    def to_xml(self):
+        """Serialize this State to xml.
+
+        Returns
+        -------
+        xml : etree.Element
+        """
+        xml = etree.Element("State")
+        if self.name is not None:
+            xml.set("name", self.name)
+        if self.model is not None:
+            xml.set("model", self.model)
+        return xml
+
+    @staticmethod
+    def from_xml(xml):
+        """Create a new State instance from an xml Element.
+
+        Parameters
+        ----------
+        xml : etree.Element
+
+        Returns
+        -------
+        state : State
+        """
+        name = xml.get("name")
+        model = xml.get("model")
+        result = State(name=name, model=model)
+        result._id = xml.get("id")
+        result._description = xml.get("description")
+        transitions = [Transition.from_xml(t) for t in xml if t.tag.lower() ==
+                       "transition"]
+        if transitions:
+            result._transitions = set(transitions)
+
+        return result
+
+    @staticmethod
+    def create_state_acl(acl):
+        from .models import ACL
+        state_acl = ACL()
+        state_acl._grants = _translate_to_state_acis(acl._grants)
+        state_acl._denials = _translate_to_state_acis(acl._denials)
+        state_acl._priority_grants = _translate_to_state_acis(acl._priority_grants)
+        state_acl._priority_denials = _translate_to_state_acis(acl._priority_denials)
+        return state_acl
diff --git a/src/caosdb/configuration.py b/src/caosdb/configuration.py
index 4d0797844182b8465ef5f97a869e31ee4fcaf47d..6e8a9c6ff2083b0c30324722003fb3c08a592191 100644
--- a/src/caosdb/configuration.py
+++ b/src/caosdb/configuration.py
@@ -28,6 +28,9 @@ except ImportError:
     # python3
     from configparser import ConfigParser
 
+from os import environ, getcwd
+from os.path import expanduser, join, isfile
+
 
 def _reset_config():
     global _pycaosdbconf
@@ -49,3 +52,20 @@ def configure(inifile):
 
 def get_config():
     return _pycaosdbconf
+
+
+def _read_config_files():
+    """Function to read config files from different paths. Checks for path in $PYCAOSDBINI or home directory (.pycaosdb.ini) and in the current working directory (pycaosdb.ini).
+
+    Returns:
+        [list]: list with successfully parsed ini-files. Order: env_var or home directory, cwd. Used for testing the function.
+    """
+    return_var = []
+    if "PYCAOSDBINI" in environ:
+        return_var.extend(configure(expanduser(environ["PYCAOSDBINI"])))
+    else:
+        return_var.extend(configure(expanduser('~/.pycaosdb.ini')))
+
+    if isfile(join(getcwd(), "pycaosdb.ini")):
+        return_var.extend(configure(join(getcwd(), "pycaosdb.ini")))
+    return return_var
diff --git a/src/caosdb/connection/connection.py b/src/caosdb/connection/connection.py
index fc699ab5db1db36bc1ee63034b6828eec4d16bc1..9e273a56778737033fda9f342f967f56946b501b 100644
--- a/src/caosdb/connection/connection.py
+++ b/src/caosdb/connection/connection.py
@@ -41,7 +41,11 @@ from caosdb.exceptions import (CaosDBException, HTTPClientError,
                                HTTPResourceNotFoundError,
                                HTTPServerError,
                                HTTPURITooLongError)
-from caosdb.version import version
+try:
+    from caosdb.version import version
+except ModuleNotFoundError:
+    version = "uninstalled"
+
 from pkg_resources import resource_filename
 
 from .interface import CaosDBHTTPResponse, CaosDBServerConnection
@@ -611,3 +615,11 @@ class _Connection(object):  # pylint: disable=useless-object-inheritance
         _handle_response_status(http_response)
 
         return http_response
+
+    def get_username(self):
+        """
+        Return the username of the current connection.
+
+        Shortcut for: get_connection()._authenticator._credentials_provider.username
+        """
+        return self._authenticator._credentials_provider.username
diff --git a/src/caosdb/exceptions.py b/src/caosdb/exceptions.py
index c9da22ce59b4eee37ea35e58d24ef436a6e88255..fdd2e11f1dfb8857f86942df2534d732bad9a793 100644
--- a/src/caosdb/exceptions.py
+++ b/src/caosdb/exceptions.py
@@ -63,6 +63,13 @@ class ConfigurationError(CaosDBException):
              ".pycaosdb.ini. Does at least one of them exist and are they correct?")
 
 
+class ServerConfigurationException(CaosDBException):
+    """The server is configured in a different way than expected.
+
+    This can be for example unexpected flags or settings or missing extensions.
+    """
+
+
 class HTTPClientError(CaosDBException):
     """HTTPClientError represents 4xx HTTP client errors."""
 
@@ -182,8 +189,8 @@ class TransactionError(CaosDBException):
         error_t. If direct_children_only is True, only direct children
         are checked.
 
-        Parameters:
-        -----------
+        Parameters
+        ----------
         error_t : EntityError
             error type to be checked
         direct_children_only: bool, optional
@@ -192,8 +199,8 @@ class TransactionError(CaosDBException):
             children, i.e., all errors in self.all_errors are
             used. Default is false.
 
-        Returns:
-        --------
+        Returns
+        -------
         has_error : bool
             True if at least one of the children is of type error_t,
             False otherwise.
diff --git a/src/caosdb/utils/caosdb_admin.py b/src/caosdb/utils/caosdb_admin.py
index c2e751d95a3f56549215465eba73e4f3129aa5c8..d926419d984f1d83e5a111ac9d72c7c8c1c74571 100755
--- a/src/caosdb/utils/caosdb_admin.py
+++ b/src/caosdb/utils/caosdb_admin.py
@@ -73,7 +73,7 @@ def do_retrieve(args):
                 c.append(db.Entity(id=eid))
             except ValueError:
                 c.append(db.Entity(name=i))
-        c.retrieve()
+        c.retrieve(flags=eval(args.flags))
     print(c)
 
 
diff --git a/src/doc/Makefile b/src/doc/Makefile
index 5458c5300efc82e55686bc1cd6934182c5c8e39a..64219c5957ee963e84f9305685f2ec4e8ed3d761 100644
--- a/src/doc/Makefile
+++ b/src/doc/Makefile
@@ -32,6 +32,7 @@ PY_BASEDIR    = ../caosdb
 SOURCEDIR     = .
 BUILDDIR      = ../../build/doc
 
+
 .PHONY: doc-help Makefile
 
 # Put it first so that "make" without argument is like "make help".
@@ -44,4 +45,4 @@ doc-help:
 	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
 
 apidoc:
-	@$(SPHINXAPIDOC) -o _apidoc $(PY_BASEDIR)
+	@$(SPHINXAPIDOC) -o _apidoc --separate $(PY_BASEDIR)
diff --git a/src/doc/administration.rst b/src/doc/administration.rst
index 91b85344a018618284349b4e8dd34fe6b365b94d..061acc8364d2ef62f743a20d7b9e6562baac0fc5 100644
--- a/src/doc/administration.rst
+++ b/src/doc/administration.rst
@@ -2,13 +2,13 @@ Administration
 ==============
 
 The Python script ``caosdb_admin.py`` should be used for administrative tasks.
-Call ``python3 caosdb_admin.py --help`` to see how to use it.
+Call ``caosdb_admin.py --help`` to see how to use it.
 
 The most common task is to create a new user (in the CaosDB realm) and set a 
 password for the user (note that a user typically needs to be activated)::
 
-     python3 caosdb_admin.py create_user anna
-     python3 caosdb_admin.py set_user_password anna
-     python3 caosdb_admin.py add_user_roles anna administration
-     python3 caosdb_admin.py activate_user anna
+     caosdb_admin.py create_user anna
+     caosdb_admin.py set_user_password anna
+     caosdb_admin.py add_user_roles anna administration
+     caosdb_admin.py activate_user anna
 
diff --git a/src/doc/conf.py b/src/doc/conf.py
index f276f325273b71d4b697bc57990259e842b2dbc3..b05fa1c71c1dcd0b59916594818449d2ebc574bd 100644
--- a/src/doc/conf.py
+++ b/src/doc/conf.py
@@ -8,16 +8,18 @@
 
 # -- Path setup --------------------------------------------------------------
 
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
+# If extensions (or modules to document with autodoc) are in another directory, add these
+# directories to sys.path here. This is particularly necessary if this package is installed at a
+# different version, for example via `pip install`.
 #
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('../caosdb'))
-
+# If the directory is relative to the documentation root, use os.path.abspath to make it absolute,
+# like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('..'))
 
-import sphinx_rtd_theme
+import sphinx_rtd_theme  # noqa: E402
 
 
 # -- Project information -----------------------------------------------------
@@ -27,9 +29,10 @@ copyright = '2020, IndiScale GmbH'
 author = 'Daniel Hornung'
 
 # The short X.Y version
-version = '0.4.0'
+version = '0.5.2'
 # The full version, including alpha/beta/rc tags
-release = '0.4.0-rc'
+# release = '0.5.2-rc2'
+release = '0.5.2'
 
 
 # -- General configuration ---------------------------------------------------
@@ -43,6 +46,7 @@ release = '0.4.0-rc'
 # ones.
 extensions = [
     'sphinx.ext.autodoc',
+    'sphinx.ext.autosectionlabel',
     'sphinx.ext.intersphinx',
     'sphinx.ext.napoleon',     # For Google style docstrings
     "recommonmark",            # For markdown files.
@@ -54,7 +58,6 @@ templates_path = ['_templates']
 
 # The suffix(es) of source filenames.
 # You can specify multiple suffix as a list of string:
-#
 source_suffix = ['.rst', '.md']
 
 # The master toctree document.
@@ -81,6 +84,7 @@ pygments_style = None
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
 #
+
 html_theme = "sphinx_rtd_theme"
 
 # Theme options are theme-specific and customize the look and feel of a theme
@@ -182,14 +186,22 @@ epub_exclude_files = ['search.html']
 
 # -- Extension configuration -------------------------------------------------
 
+# True to prefix each section label with the name of the document it is in, followed by a colon. For
+# example, index:Introduction for a section called Introduction that appears in document
+# index.rst. Useful for avoiding ambiguity when the same section heading appears in different
+# documents.
+#
+# Note: This stops "normal" links from working, so it should be kept at False.
+# autosectionlabel_prefix_document = True
+
 # -- Options for intersphinx -------------------------------------------------
 
 # https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#confval-intersphinx_mapping
 intersphinx_mapping = {
     "python": ("https://docs.python.org/", None),
-    "caosdb-mysqlbackend": ("https://caosdb.gitlab.io/caosdb-mysqlbackend/",
+    "caosdb-mysqlbackend": ("https://docs.indiscale.com/caosdb-mysqlbackend/",
                             None),
-    "caosdb-server": ("https://caosdb.gitlab.io/caosdb-server/", None),
+    "caosdb-server": ("https://docs.indiscale.com/caosdb-server/", None),
 }
 
 
diff --git a/src/doc/configuration.md b/src/doc/configuration.md
index b2de2781d5adff4d59cb3648cd912e142327f676..6e53542f661dcae94622fef24a67cecf7491df9c 100644
--- a/src/doc/configuration.md
+++ b/src/doc/configuration.md
@@ -50,5 +50,5 @@ debugging (which I hope will not be necessary for this tutorial) or if you want
 the internals of the protocol. 
 
 A complete list of options can be found in the 
-[pycaosdb.ini file](https://gitlab.com/caosdb/caosdb-pylib/-/blob/master/examples/pycaosdb.ini) in 
+[pycaosdb.ini file](https://gitlab.com/caosdb/caosdb-pylib/-/blob/main/examples/pycaosdb.ini) in 
 the examples folder of the source code.
diff --git a/src/doc/index.rst b/src/doc/index.rst
index 76a2f88f6d31dd9b5f17995b6d54ccc63eb33631..bd29c6c56acf5c173e94ae6471a6aeba56ea4b93 100644
--- a/src/doc/index.rst
+++ b/src/doc/index.rst
@@ -12,12 +12,12 @@ Welcome to PyCaosDB's documentation!
    Concepts <concepts>
    Configuration <configuration>
    Administration <administration>
-   API documentation<_apidoc/modules>
+   API documentation<_apidoc/caosdb>
 
 This is the documentation for the Python client library for CaosDB, ``PyCaosDB``.
 
-This documentation helps you to :doc:`get started<getting_started>`, explains the most important
-:doc:`concepts<concepts>` and offers a range of :doc:`tutorials<tutorials>`.
+This documentation helps you to :doc:`get started<README_SETUP>`, explains the most important
+:doc:`concepts<concepts>` and offers a range of :doc:`tutorials<tutorials/index>`.
 
 
 Indices and tables
diff --git a/src/doc/tutorials/Data-Insertion.rst b/src/doc/tutorials/Data-Insertion.rst
index 2ead1cc259c807ee12f2459f7e452558eb1b63a2..22fb9461d6916003b2dad496ff3487df335c8dcc 100644
--- a/src/doc/tutorials/Data-Insertion.rst
+++ b/src/doc/tutorials/Data-Insertion.rst
@@ -4,9 +4,9 @@ Data Insertion
 Data Models
 ~~~~~~~~~~~
 
-Data is stored and structured in CaosDB using a concept of RecordTypes,
-Properties, Records etc. If you do not know what these are, please look
-at the chapter :any:`caosdb-server:Data Model` .
+Data is stored and structured in CaosDB using a concept of RecordTypes, Properties, Records etc. If
+you do not know what these are, please look at the chapter :doc:`Data
+Model<caosdb-server:Data-Model>` in the CaosDB server documentation.
 
 In order to insert some actual data, we need to create a data model
 using RecordTypes and Properties (You may skip this if you use a CaosDB
@@ -35,6 +35,42 @@ two more Properties and a RecordType:
    container.extend([a, b, epsilon, recordtype])
    container.insert()
 
+.. _tutorial-inheritance-properties:
+
+Inheritance of Properties
+-------------------------
+
+Suppose you want to create a new RecordType “2D_BarkleySimulation”
+that denotes spatially extended Barkley simulations. This is a subtype
+of the “BarkleySimulation” RecordType above and should have all its
+parameters, i.e., properties. It may be assigned more, e.g., spatial
+resolution, but we'll omit this for the sake of brevity for now.
+
+.. code:: python
+
+   rt = db.RecordType(name="2D_BarkleySimulation",
+	              description="Spatially extended Barkley simulation")
+   # inherit all properties from the BarkleySimulation RecordType
+   rt.add_parent(name="BarkleySimulation", inheritance="all")
+   rt.insert()
+       
+   print(rt.get_property(name="epsilon").importance) ### rt has a "epsilon" property with the same importance as "BarkleySimulation"
+
+The parameter ``inheritance=(obligatory|recommended|fix|all|none)`` of
+:py:meth:`Entity.add_parent()<caosdb.common.models.Entity.add_parent>` tells the server to assign
+all properties of the parent RecordType with the chosen importance (and properties with a higher
+importance) to the child RecordType
+automatically upon insertion. See the chapter on `importance
+<https://docs.indiscale.com/caosdb-server/specification/RecordType.html#importance>`_ in the
+documentation of the CaosDB server for more information on the importance and inheritance of
+properties.
+
+.. note::
+
+   The inherited properties will only be visible after the insertion since they are set by the
+   CaosDB server, not by the Python client.
+
+
 Insert Actual Data
 ~~~~~~~~~~~~~~~~~~
 
@@ -108,32 +144,6 @@ Useful is also, that you can insert directly tabular data.
 With this example file
 `test.csv <uploads/4f2c8756a26a3984c0af09d206d583e5/test.csv>`__.
 
-Inheritance of Properties
--------------------------
-
-Given, you want to insert a new RecordType “Fridge temperatur
-experiment” as a child of the existing RecordType “Experiment”. The
-latter may have an obligatory Property “date” (since every experiment is
-conducted at some time). It is a natural way of thinking, that every sub
-type of “Experiment” also has this obligatory Property—in terms of
-object oriented programing the “Fridge temperatur experiment” *inherits*
-that Property.
-
-::
-
-       rt = h.RecordType(name="Fridge temperatur experiment", 
-                                 description="RecordType which inherits all obligatory properties from Experiment"
-                                 ).add_parent(name="Experiment", inheritance="obligatory").insert()
-       
-       print(rt.get_property(name="date").importance) ### rt now has a "date"-property -> this line prints "obligatory"
-
-The parameter *``inheritance=(obligatory|recommended|fix|all|none)``* of
-``add_parent`` tells the server to assign obligatory:: properties of the
-parent to the child automatically, recommended:: properties of the
-parent to the child automatically, fix:: properties of the parent to the
-child automatically, all:: properties of the parent to the child
-automatically, none:: of the properties of the parent to child
-automatically,
 
 File Update
 -----------
diff --git a/src/doc/tutorials/errors.rst b/src/doc/tutorials/errors.rst
index ba386dc31baad1021f01d53b2b12a2623d8278ad..37c53c9b527a0435f9f24ae6c6e71687e73eb963 100644
--- a/src/doc/tutorials/errors.rst
+++ b/src/doc/tutorials/errors.rst
@@ -1,53 +1,176 @@
 
 Error Handling
---------------
+==============
 
-HeartDBException
-~~~~~~~~~~~~~~~~
+In case of erroneous transactions, connection problems and a lot of
+other cases, PyCaosDB may raise specific errors in order to pinpoint
+the problem as precisely as possible. Some of these errors a
+representations of errors in the CaosDB server, others stem from
+problems that occurred on the client side.
+
+The errors and exceptions are ordered hierarchically form the most
+general exceptions to specific transaction or connection problems. The
+most important error types and the hierarchy will be explained in the
+following. For more information on specific error types, see also the
+:doc:`source code<../_apidoc/caosdb.exceptions>`.
+
+.. note::
+
+   Starting from PyCaosDB 0.5, the error handling has changed
+   significantly. New error classes have been introduced and the
+   behavior of ``TransactionError`` and ``EntityError`` has been
+   re-worked. In the following, only the "new" errors are
+   discussed. Please refer to the documentation of PyCaosDB 0.4.1 and
+   earlier for the old error handling.
+
+CaosDBException
+----------------
+
+``CaosDBException`` is the most generic exception and all other error classes inherit
+from this one. Because of its generality, it doesn't tell you much
+except that some component of PyCaosDB raised an exception. If you
+want to catch all possible CaosDB errors, this is the class to use.
 
 TransactionError
-~~~~~~~~~~~~~~~~
+----------------
 
 Every transaction (calling ``insert``, ``update``, ``retrieve``, or
 ``delete`` on a container or an entity) may finish with errors. They
 indicate, for instance, that an entity does not exist or that you need
 to specify a data type for your property and much more. If and only if
 one or more errors occur during a transaction a ``TransactionError``
-will be raised by the transaction method. The ``TransactionError`` class
-is a container for all errors which occur during a transaction. It can
-help you to find the crucial problems with your transaction by two
-important methods: \* ``get_errors()`` which returns a list of instances
-of ``EntityError``. \* ``get_entities()`` which returns a list of
-entities in the transaction container which are erroneous.
-
-Additionally, ``print(transaction_error`` prints a tree-like
+will be raised by the transaction method. The ``TransactionError``
+class is a container for all errors which occur during a
+transaction. It usually contains one or more :ref:`entity
+errors<EntityError>` which you can inspect in order to learn why the
+transaction failed. For this inspection, there are some helpful
+attributes and methods provided by the ``TransactionError``:
+
+* ``entities``: a list of all entities that directly caused at least one error
+  in this transaction.
+
+* ``errors``: a list of all ``EntityError`` objects that directly caused the
+  transaction to fail.
+
+* ``all_entities``, ``all_errors``: sets of all entities and errors
+  that, directly or indirectly, caused either this ``TransactionError`` or any of the
+  ``EntityError`` objects it contains.
+
+* ``has_error(error_t)``: Check whether an error of type ``error_t``
+  occurred during the transaction.
+
+Additionally, ``print(transaction_error)`` prints a tree-like
 representation of all errors regarding the transaction in question.
 
 EntityError
-~~~~~~~~~~~
+-----------
 
-An ``EntityError`` represents a single error that has been returned by
-the server. You might call \* ``get_entity()`` which returns the entity
-which caused the error. \* ``get_description()`` which returns a
-description of the error. \* ``get_code()`` which returns the error code
-(if specified) or 0 (if not).
+An ``EntityError`` specifies the entity and the error proper that
+caused a transaction to fail. It is never raised on its own but is
+contained in a ``TransactionError`` (which may or may not contain
+other ``EntityError`` objects) which is then raised. ``EntityError``
+has several :ref:`subclasses<Special Errors>` that further specify the
+error that occurred.
 
-In fact, the ``EntityError`` class is a subclass of
-``TransactionError``. So, it inherits the ``get_entities()``. Unless
-overridden by subclasses of ``EntityError``, it return a list with only
-one item—the entity which caused this error. Similarly, unless
-overridden by subclasses, the ``get_errors()`` method returns a list
-with only one item—``[self]``.
+The ``EntityError`` class is in fact a subclass of
+``TransactionError``. Thus, it has the same methods and attributes as
+the ``TransactionError`` explained
+:ref:`above<TransactionError>`. This is important in case of an
+``EntityError`` that was caused by other faulty entities (e.g., broken
+parents or properties). In that case these problematic entities and
+errors can again be inspected by visiting the ``entities`` and
+``errors`` lists as above.
 
 Special Errors
 ~~~~~~~~~~~~~~
 
-Subclasses of ``EntityError`` for special purposes: \*
-``EntityDoesNotExistError`` \* ``EntityHasNoDataTypeError`` \*
-``UniqueNamesError`` \* ``UnqualifiedParentsError`` - overrides
-``get_entities()``: returns all parent entities with errors. - overrides
-``get_errors()``: returns a list of EntityErrors which have been caused
-by parent entities. \* ``UnqualifiedPropertiesError`` - overrides
-``get_entities()``: returns all properties with errors. - overrides
-``get_errors()``: returns a list of EntityErrors which have been caused
-by properties.
+Subclasses of ``EntityError`` for special purposes:
+
+* ``EntityDoesNotExistError``
+
+* ``EntityHasNoDataTypeError``
+
+* ``UniqueNamesError``
+
+* ``UnqualifiedParentsError``
+
+* ``UnqualifiedPropertiesError``
+
+* ``ConsistencyError``
+
+* ``AuthorizationError``
+
+* ``AmbiguousEntityError``
+
+BadQueryError
+-------------
+
+A ``BadQueryError`` is raised when a query could not be processed by
+the server. In contrast to a ``TransactionError`` it is not
+necessarily caused by problematic entities or
+containers. ``BadQueryError`` has the two important subclasses
+``EmptyUniqueQueryError`` and ``QueryNotUniqueError`` for queries with
+``unique=True`` which found no or ambiguous entities, respectively.
+
+HTTP Errors
+-----------
+
+An ``HTTPClientError`` or an ``HTTPServerError`` is raised in case of
+http(s) connection problems caused by the Python client or the CaosDB
+server, respectively. There are the following subclasses of
+``HTTPClientError`` that are used to specify the connection problem:
+
+* ``HTTPURITooLongError``: The URI of the request was too long to be
+  processed by the server.
+
+* ``HTTPForbiddenError``: You're not allowed to access this resource.
+
+* ``HTTPResourceNotFoundError``: The requested resource doesn't exist.
+
+Other Errors
+------------
+
+There are further subclasses of ``CaosDBException`` that are raised in
+case of faulty configurations or other problems. They should be rather
+self-explanatory from their names; see the :doc:`source code<../_apidoc/caosdb.exceptions>`
+for further information.
+
+* ``ConfigurationError``
+
+* ``LoginFailedError``
+
+* ``MismatchingEntitiesError``
+
+* ``ServerConfigurationException``
+
+Examples
+--------
+
+.. code-block:: python3
+
+   import caosdb as db
+
+   def link_and_insert(entity, linked, link=True):
+     """Link the ENTITY to LINKED and insert it."""
+     if link:
+       entity.add_property(db.Property(name="link", value=linked))
+     try:
+       entity.insert()
+     except db.TransactionError as tre:
+       # Unique names problem may be worked around by using another name
+       if tre.has_error(db.UniqueNamesError):
+         for ent_error in tre.errors:
+           if (isinstance(ent_error, db.UniqueNamesError)
+               and entity in ent_error.entities):
+             entity.name = entity.name + "_new"  # Try again with new name.
+             link_and_insert(entity, linked, link=False)
+             break
+       # Unqualified properties will be handled by the caller
+       elif tre.has_error(db.UnqualifiedPropertiesError):
+         for ent_error in tre.errors:
+           if (isinstance(ent_error, db.UnqualifiedPropertiesError_
+               and entity in ent_error.entities):
+             raise RuntimeError("One of the properties was unqualified: " + str(ent_error))
+       # Other problems are not covered by this tutorial
+       else:
+         raise NotImplementedError("Unhandled TransactionError: " + str(tre))
diff --git a/src/doc/tutorials/first_steps.rst b/src/doc/tutorials/first_steps.rst
index 3eb804776960ba93acb9baf78824f0cf8a06e1f7..34b96bbeca416107fb34feb4707b9ef46fc49fe7 100644
--- a/src/doc/tutorials/first_steps.rst
+++ b/src/doc/tutorials/first_steps.rst
@@ -2,14 +2,15 @@ First Steps
 ===========
 
 You should have a working connection to a CaosDB instance now. If not, please check out the 
-:doc:`Getting Started secton</getting_started>`.
+:doc:`Getting Started secton</README_SETUP>`.
 
 If you are not yet familiar with Records, RecordTypes and Properties used in CaosDB,
-please check out the respective part in the `Web Interface Tutorial`_. 
-You should also know the basics of the CaosDB Query Language (a tutorial is here_).
+please check out the respective part in the `Web Interface tutorial`_.
+You should also know the basics of the CaosDB Query Language (a tutorial is
+`here <https://docs.indiscale.com/caosdb-webui/tutorials/query.html>`_).
 
-We recommend that you connect to the demo instance in order to try out the following
-examples. You can do this with
+We recommend that you connect to the `demo instance`_ (hosted by `Indiscale`_) in order to try out
+the following examples. You can do this with
 
 >>> import caosdb as db
 >>> _ = db.configure_connection(
@@ -19,7 +20,7 @@ examples. You can do this with
 ...    password="caosdb")
 
 or by using corresponding settings in the configuration file
-(see :doc:`Getting Started secton</getting_started>`.). 
+(see :doc:`Getting Started secton</README_SETUP>`.).
 However, you can also translate the examples to the data model that you have at hand.
 
 Let's start with a simple query.
@@ -124,9 +125,6 @@ the result Records and their properties.
 The next tutorial shows how to make some meaningful use of this.
 
 
-
-.. _here: https://gitlabio.something
 .. _`demo instance`: https://demo.indiscale.com
 .. _`IndiScale`: https://indiscale.com
-.. _`Web Interface Tutorial`: https://caosdb.gitlab.io/caosdb-webui/tutorials/model.html
-.. _here: https://caosdb.gitlab.io/caosdb-webui/tutorials/cql.html
+.. _`Web Interface tutorial`: https://docs.indiscale.com/caosdb-webui/tutorials/first_steps.html
diff --git a/unittests/docker/Dockerfile b/unittests/docker/Dockerfile
index e41fb91b3e9ae54cff277519c03f481c21264e9e..7fa3f75bd198724628dee48ab328829fa071a639 100644
--- a/unittests/docker/Dockerfile
+++ b/unittests/docker/Dockerfile
@@ -5,6 +5,6 @@ RUN apt-get update && \
       curl pycodestyle \
       python3-sphinx
 ARG COMMIT="dev"
-RUN git clone -b dev https://gitlab.com/caosdb/caosdb-pylib.git && \
+RUN git clone -b dev https://gitlab.indiscale.com/caosdb/src/caosdb-pylib.git && \
     cd caosdb-pylib && git checkout $COMMIT && pip3 install .
 RUN pip3 install recommonmark sphinx-rtd-theme
diff --git a/unittests/test_configuration.py b/unittests/test_configuration.py
index 76445b6f262120d6a29c73527a9bf042f85f8a05..b135e7cd65b11be7cb6c4ef2237a41a6639ccbb7 100644
--- a/unittests/test_configuration.py
+++ b/unittests/test_configuration.py
@@ -22,19 +22,45 @@
 # ** end header
 #
 
+import pytest
 import caosdb as db
+from os import environ, getcwd, remove
+from os.path import expanduser, isfile, join
 from pytest import raises
 
 
-def test_config_ini_via_envvar():
-    from os import environ
-    from os.path import expanduser
+@pytest.fixture
+def temp_ini_files():
+    created_temp_ini_cwd = False
+    created_temp_ini_home = False
+    if not isfile(join(getcwd(), "pycaosdb.ini")):
+        open("pycaosdb.ini", 'a').close()  # create temporary ini file
+        created_temp_ini_cwd = True
+    if not isfile(expanduser("~/.pycaosdb.ini")):
+        open(expanduser("~/.pycaosdb.ini"), 'a').close()  # create temporary ini file in home directory
+        created_temp_ini_home = True
+    yield 0
+    if created_temp_ini_cwd:
+        remove("pycaosdb.ini")
+    if created_temp_ini_home:
+        remove(expanduser("~/.pycaosdb.ini"))
+    environ["PYCAOSDBINI"] = "~/.pycaosdb.ini"
+
+
+def test_config_ini_via_envvar(temp_ini_files):
 
     with raises(KeyError):
         environ["PYCAOSDBINI"]
 
     environ["PYCAOSDBINI"] = "bla bla"
     assert environ["PYCAOSDBINI"] == "bla bla"
-    assert db.configuration.configure(environ["PYCAOSDBINI"]) == []
+    # test wrong configuration file in envvar
+    assert not expanduser(environ["PYCAOSDBINI"]) in db.configuration._read_config_files()
+    # test good configuration file in envvar
     environ["PYCAOSDBINI"] = "~/.pycaosdb.ini"
-    assert db.configuration.configure(expanduser(environ["PYCAOSDBINI"])) == [expanduser("~/.pycaosdb.ini")]
+    assert expanduser("~/.pycaosdb.ini") in db.configuration._read_config_files()
+    # test without envvar
+    environ.pop("PYCAOSDBINI")
+    assert expanduser("~/.pycaosdb.ini") in db.configuration._read_config_files()
+    # test configuration file in cwd
+    assert join(getcwd(), "pycaosdb.ini") in db.configuration._read_config_files()
diff --git a/unittests/test_query.py b/unittests/test_query.py
index f4b3ee9762d9d76b65ace9a5b9b3f4039c0c6919..12622ea486dda717ca1fbc1255510575c5e0c8e6 100644
--- a/unittests/test_query.py
+++ b/unittests/test_query.py
@@ -26,20 +26,23 @@ import caosdb as db
 
 
 def test_query_parsing():
-    s = '<Query string="FIND bla" results="0" cached="true"/>'
+    s = '<Query string="FIND bla" results="0" cached="true" etag="asdf"/>'
     q = db.Query(etree.fromstring(s))
     assert q.q == "FIND bla"
     assert q.results == 0
     assert q.cached is True
+    assert q.etag == "asdf"
 
-    s = '<Query string="COUNT bla" results="1" cached="false"/>'
+    s = '<Query string="COUNT bla" results="1" cached="false" etag="asdf"/>'
     q = db.Query(etree.fromstring(s))
     assert q.q == "COUNT bla"
     assert q.results == 1
     assert q.cached is False
+    assert q.etag == "asdf"
 
     s = '<Query string="COUNT blub" results="4"/>'
     q = db.Query(etree.fromstring(s))
     assert q.q == "COUNT blub"
     assert q.results == 4
     assert q.cached is False
+    assert q.etag is None
diff --git a/unittests/test_state.py b/unittests/test_state.py
new file mode 100644
index 0000000000000000000000000000000000000000..202c7a02af3db28434406626e5164def46febed7
--- /dev/null
+++ b/unittests/test_state.py
@@ -0,0 +1,77 @@
+import pytest
+import caosdb as db
+from caosdb import State, Transition
+from caosdb.common.models import parse_xml, ACL
+from lxml import etree
+
+
+def test_state_xml():
+    state = State(model="model1", name="state1")
+    xml = etree.tostring(state.to_xml())
+
+    assert xml == b'<State name="state1" model="model1"/>'
+    state = State.from_xml(etree.fromstring(xml))
+    assert state.name == "state1"
+    assert state.model == "model1"
+
+    assert xml == etree.tostring(state.to_xml())
+
+
+def test_entity_xml():
+    r = db.Record()
+    assert r.state is None
+    r.state = State(model="model1", name="state1")
+
+    xml = etree.tostring(r.to_xml())
+    assert xml == b'<Record><State name="state1" model="model1"/></Record>'
+
+    r = parse_xml(xml)
+    assert r.state == State(model="model1", name="state1")
+
+
+def test_description():
+    xml = b'<State name="state1" model="model1"/>'
+    state = State.from_xml(etree.fromstring(xml))
+    assert state.description is None
+
+    with pytest.raises(AttributeError):
+        state.description = "test"
+
+    xml = b'<State name="state1" model="model1" description="test2"/>'
+    state = State.from_xml(etree.fromstring(xml))
+    assert state.description == "test2"
+
+
+def test_id():
+    xml = b'<State name="state1" model="model1"/>'
+    state = State.from_xml(etree.fromstring(xml))
+    assert state.id is None
+
+    with pytest.raises(AttributeError):
+        state.id = "2345"
+
+    xml = b'<State name="state1" model="model1" id="1234"/>'
+    state = State.from_xml(etree.fromstring(xml))
+    assert state.id == "1234"
+
+
+def test_create_state_acl():
+    acl = ACL()
+    acl.grant(role="role1", permission="DO:IT")
+    acl.grant(role="?OWNER?", permission="DO:THAT")
+    state_acl = State.create_state_acl(acl)
+    assert state_acl.get_permissions_for_role("?STATE?role1?") == {"DO:IT"}
+    assert state_acl.get_permissions_for_role("?STATE??OWNER??") == {"DO:THAT"}
+
+
+def test_transitions():
+    xml = b'<State name="state1" model="model1"/>'
+    state = State.from_xml(etree.fromstring(xml))
+    assert state.transitions is None
+
+    with pytest.raises(AttributeError):
+        state.transitions = []
+
+    xml = b'<State name="state1" model="model1" id="1234"><Transition name="t1"><FromState name="state1"/><ToState name="state2"/></Transition></State>'
+    state = State.from_xml(etree.fromstring(xml))
+    assert state.transitions == set([Transition(name="t1", from_state="state1", to_state="state2")])