diff --git a/.docs/api/data-service.md b/.docs/api/data-service.md
index 257c68c1957fa697957c9dfc3f6b681693ac3f9d..bc43a5d3631f7a9b4ecd85c64472bd25095a9603 100644
--- a/.docs/api/data-service.md
+++ b/.docs/api/data-service.md
@@ -24,25 +24,32 @@ author: Martin Weise
 
 ## Overview
 
-The Data Service is responsible for inserting AMQP tuples from the Broker Service into the Data DB 
+The Data Service is responsible for inserting AMQP tuples from the Broker Service into the Data DB
 via [Spring AMQP](https://docs.spring.io/spring-amqp/reference/html/). To increase the number of consumers, scale the
 Data Service up.
 
 ## Data Processing
 
-The Data Service uses [Apache Spark](https://spark.apache.org/), a data engine to load data from/into 
+The Data Service uses [Apache Spark](https://spark.apache.org/), a data engine to load data from/into
 the [Data Database](../data-db) with a wide range of open-source connectors. The default deployment uses a local mode of
-embedded processing directly in the service until there exists 
+embedded processing directly in the service until there exists
 a [Bitnami Chart](https://artifacthub.io/packages/helm/bitnami/spark) for Spark 4.
 
 Retrieving data from a subset internally generates a view with the 64-character hash of the query. This view is not
 automatically deleted currently.
 
+## Caching
+
+The Data Service uses [Caffeine](https://github.com/ben-manes/caffeine), a caching solution that is used to temporarily
+cache the connection details from the [Metadata Service](../metadata-service) such that they don't have to be queried
+everytime e.g. a sensor measurement is inserted. By default, this information is stored for 60 minutes. System
+administrators can disable this behavior by setting `CREDENTIAL_CACHE_TIMEOUT=0` (cache is deleted after 0 seconds).
+
 ## Limitations
 
-* Views in DBRepo can only have 63-character length (it is assumed only internal views have the maximum length of 64 
+* Views in DBRepo can only have 63-character length (it is assumed only internal views have the maximum length of 64
   characters).
-* Local mode of embedded processing of Apache Spark directly in the service using 
+* Local mode of embedded processing of Apache Spark directly in the service using
   a [`local[2]`](https://spark.apache.org/docs/latest/#running-the-examples-and-shell) configuration.
 
 !!! question "Do you miss functionality? Do these limitations affect you?"
diff --git a/.docs/concepts/data-visibility.md b/.docs/concepts/data-visibility.md
index e76448ec3eaddb09ecf7d601d37433f641f19751..04f37c6979bd9019d954859535a757c72da4b63e 100644
--- a/.docs/concepts/data-visibility.md
+++ b/.docs/concepts/data-visibility.md
@@ -2,32 +2,34 @@
 author: Martin Weise
 ---
 
-There are several ways to set the visibility of (meta-)data in DBRepo. It is possible to set the data to public/private
-and the schema to be public/private for each database and separately for each table, each view and each subset of a
-database. 
+There are several ways to set the visibility of (meta-)data in DBRepo. It is possible to set the data visibility to
+visible/hidden and the schema to be visible/hidden for each database and separately for each table, each view and each
+subset of a database.
+
+## Visibility
 
 In total there are three possible scenarios:
 
-## Public
+#### Public
 
 !!! info "Possible use-case: data publication supplement to an open-access publication"
 
-Where the database's data and metadata is set to be *public*. This means everything in the database (tables, views,
+Where the database's data and metadata is set to be *visible*. This means everything in the database (tables, views,
 subsets) are visible by anyone from the public.
 
-## Mixed
+#### Private
 
 !!! info "Possible use-case: private sensor measurements with timed embargo"
 
-Where the database's data and metadata is set to be *private*. This means everything in the database (tables, views,
-subsets) are by default not visible by anyone from the public. You can however make specific views that join tables
-and/or filter certain columns and apply a 14-day delay-embargo.
+Where the database's data set to be *hidden* but the schema to be *visible*. This means everything in the database
+(tables, views, subsets) are by default not visible by anyone from the public. You can however make specific views that
+join tables and/or filter certain columns and apply a 14-day delay-embargo.
 
 <figure markdown>
 ![Mirroring statistical properties in Metadata Database and Search Database](../images/private-embargo.svg)
 <figcaption>Figure 1: Public view that joins two private tables and applies a time-embargo</figcaption>
 </figure>
 
-## Private
+#### Draft
 
-!!! info "Possible use-case: data storage for trusted-/virtual research environments"
\ No newline at end of file
+!!! info "Possible use-case: project data storage before publication"
\ No newline at end of file
diff --git a/dbrepo-analyse-service/Pipfile.lock b/dbrepo-analyse-service/Pipfile.lock
index 80237c91cfb007d4da0a9bad42220c798622604f..99d70b613229b3fcc81638d99d038034b58ef9d6 100644
--- a/dbrepo-analyse-service/Pipfile.lock
+++ b/dbrepo-analyse-service/Pipfile.lock
@@ -412,7 +412,7 @@
         },
         "dbrepo": {
             "hashes": [
-                "sha256:79ae690b125dcc6ba7ada28f07af47bca6e943ae3079232b3b4ae2d369095bb5"
+                "sha256:7cddcbdcb3eade84f67db01fa32e0649ecc01d4c3cc5e7542d3c402ad52efc19"
             ],
             "path": "./lib/dbrepo-1.6.1.tar.gz"
         },
@@ -1612,7 +1612,7 @@
                 "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
                 "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
             ],
-            "markers": "python_version >= '3.9'",
+            "markers": "python_version >= '3.10'",
             "version": "==2.3.0"
         },
         "werkzeug": {
@@ -2236,7 +2236,7 @@
                 "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
                 "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
             ],
-            "markers": "python_version >= '3.9'",
+            "markers": "python_version >= '3.10'",
             "version": "==2.3.0"
         },
         "wrapt": {
diff --git a/dbrepo-analyse-service/lib/dbrepo-1.6.1.tar.gz b/dbrepo-analyse-service/lib/dbrepo-1.6.1.tar.gz
index 6e6884a28d818166eec6ded367274d57b137f4af..a1197ff6465d77567108f3389aae9d54dc9c8ab6 100644
Binary files a/dbrepo-analyse-service/lib/dbrepo-1.6.1.tar.gz and b/dbrepo-analyse-service/lib/dbrepo-1.6.1.tar.gz differ
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
index 8194ca624cde4b55f407e01ed716b899eb6c69b7..8a106b6cfdd3b6d3117617348f5d592df5144181 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java
@@ -76,7 +76,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
     @GetMapping
     @Observed(name = "dbrepo_subset_list")
     @Operation(summary = "Find subsets",
-            description = "Finds subsets in the query store. The result can be optionally filtered by setting `persisted`. When set to *true*, only persisted queries are returned, otherwise only non-persisted queries are returned.",
+            description = "Finds subsets in the query store. When the database schema is marked as hidden, the user needs to be authorized, have at least read-access to the database and have the role `list-queries`. The result can be optionally filtered by setting `persisted`. When set to *true*, only persisted queries are returned, otherwise only non-persisted queries are returned.",
             security = {@SecurityRequirement(name = "basicAuth"), @SecurityRequirement(name = "bearerAuth")})
     @ApiResponses(value = {
             @ApiResponse(responseCode = "200",
@@ -107,13 +107,8 @@ public class SubsetEndpoint extends AbstractEndpoint {
             QueryNotFoundException, NotAllowedException, MetadataServiceException {
         log.debug("endpoint find subsets in database, databaseId={}, filterPersisted={}", databaseId, filterPersisted);
         final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId);
-        if (!database.getIsPublic() || !database.getIsSchemaPublic()) {
-            if (principal == null) {
-                log.error("Failed to find subset: database is private & missing authentication");
-                throw new NotAllowedException("Failed to find subset: database is private & missing authentication");
-            }
-            metadataServiceGateway.getAccess(databaseId, getId(principal));
-        }
+        endpointValidator.validateOnlyPrivateSchemaAccess(database, principal);
+        endpointValidator.validateOnlyPrivateSchemaHasRole(database, principal, "list-queries");
         final List<QueryDto> queries;
         try {
             queries = subsetService.findAll(database, filterPersisted);
@@ -128,7 +123,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
     @GetMapping("/{subsetId}")
     @Observed(name = "dbrepo_subset_find")
     @Operation(summary = "Find subset",
-            description = "Finds a subset in the data database. Requests with HTTP header `Accept=application/json` return the metadata, requests with HTTP header `Accept=text/csv` return the data as downloadable file.",
+            description = "Finds a subset in the data database.  When the database schema is marked as hidden, the user needs to be authorized, have at least read-access to the database and have the role `find-query`.  Requests with HTTP header `Accept=application/json` return the metadata, requests with HTTP header `Accept=text/csv` return the data as downloadable file.",
             security = {@SecurityRequirement(name = "basicAuth"), @SecurityRequirement(name = "bearerAuth")})
     @ApiResponses(value = {
             @ApiResponse(responseCode = "200",
@@ -175,13 +170,8 @@ public class SubsetEndpoint extends AbstractEndpoint {
         log.debug("endpoint find subset in database, databaseId={}, subsetId={}, accept={}, timestamp={}", databaseId,
                 subsetId, accept, timestamp);
         final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId);
-        if (!database.getIsPublic() || !database.getIsSchemaPublic()) {
-            if (principal == null) {
-                log.error("Failed to find subset: database is private & missing authentication");
-                throw new NotAllowedException("Failed to find subset: database is private & missing authentication");
-            }
-            metadataServiceGateway.getAccess(databaseId, getId(principal));
-        }
+        endpointValidator.validateOnlyPrivateSchemaAccess(database, principal);
+        endpointValidator.validateOnlyPrivateSchemaHasRole(database, principal, "find-query");
         final QueryDto subset;
         try {
             subset = subsetService.findById(database, subsetId);
@@ -194,7 +184,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
             timestamp = Instant.now();
             log.debug("timestamp not set: default to {}", timestamp);
         }
-        if (accept == null) {
+        if (accept == null || accept.isEmpty() || accept.isBlank()) {
             accept = MediaType.APPLICATION_JSON_VALUE;
             log.debug("accept header not set: default to {}", accept);
         }
@@ -219,7 +209,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
                     throw new DatabaseUnavailableException("Failed to find data: " + e.getMessage(), e);
                 }
         }
-        throw new FormatNotAvailableException("Must provide either application/json or text/csv headers");
+        throw new FormatNotAvailableException("Must provide either application/json or text/csv value for header 'Accept': provided " + accept + " instead");
     }
 
     @PostMapping
@@ -303,13 +293,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
         }
         /* create */
         final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId);
-        if (!database.getIsPublic() || !database.getIsSchemaPublic()) {
-            if (principal == null) {
-                log.error("Failed to find subset: database is private & missing authentication");
-                throw new NotAllowedException("Failed to find subset: database is private & missing authentication");
-            }
-            metadataServiceGateway.getAccess(databaseId, getId(principal));
-        }
+        endpointValidator.validateOnlyPrivateSchemaAccess(database, principal);
         try {
             final Long subsetId = subsetService.create(database, data.getStatement(), timestamp, userId);
             return getData(databaseId, subsetId, principal, request, page, size);
@@ -375,6 +359,7 @@ public class SubsetEndpoint extends AbstractEndpoint {
             }
             credentialService.getAccess(databaseId, getId(principal));
         }
+        log.trace("visibility for database: is_public={}, is_schema_public={}", database.getIsPublic(), database.getIsSchemaPublic());
         /* parameters */
         if (page == null) {
             page = 0L;
diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
index 1c6adfd6a5b87355e76f9b40147fe91f5de4d4e2..25b858f51bd3f643f7879b9eac2dbb648b00aabb 100644
--- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
+++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java
@@ -1,14 +1,17 @@
 package at.tuwien.validation;
 
 import at.tuwien.api.database.AccessTypeDto;
+import at.tuwien.api.database.DatabaseAccessDto;
+import at.tuwien.api.database.internal.PrivilegedDatabaseDto;
 import at.tuwien.config.QueryConfig;
-import at.tuwien.exception.NotAllowedException;
-import at.tuwien.exception.PaginationException;
-import at.tuwien.exception.QueryNotSupportedException;
+import at.tuwien.endpoints.AbstractEndpoint;
+import at.tuwien.exception.*;
+import at.tuwien.gateway.MetadataServiceGateway;
 import lombok.extern.log4j.Log4j2;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.security.Principal;
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
@@ -18,13 +21,15 @@ import java.util.regex.Pattern;
 
 @Log4j2
 @Component
-public class EndpointValidator {
+public class EndpointValidator extends AbstractEndpoint {
 
     private final QueryConfig queryConfig;
+    private final MetadataServiceGateway metadataServiceGateway;
 
     @Autowired
-    public EndpointValidator(QueryConfig queryConfig) {
+    public EndpointValidator(QueryConfig queryConfig, MetadataServiceGateway metadataServiceGateway) {
         this.queryConfig = queryConfig;
+        this.metadataServiceGateway = metadataServiceGateway;
     }
 
     public void validateDataParams(Long page, Long size) throws PaginationException {
@@ -43,6 +48,56 @@ public class EndpointValidator {
         }
     }
 
+    public void validateOnlyPrivateSchemaAccess(PrivilegedDatabaseDto database, Principal principal)
+            throws NotAllowedException, RemoteUnavailableException, MetadataServiceException {
+        validateOnlyPrivateSchemaAccess(database, principal, false);
+    }
+
+    public void validateOnlyPrivateSchemaAccess(PrivilegedDatabaseDto database, Principal principal,
+                                                boolean writeAccessOnly) throws NotAllowedException,
+            RemoteUnavailableException, MetadataServiceException {
+        if (database.getIsSchemaPublic()) {
+            log.trace("database schema with id {} is public: no access needed", database.getId());
+            return;
+        }
+        validateOnlyAccess(database, principal, writeAccessOnly);
+    }
+
+    public void validateOnlyPrivateSchemaHasRole(PrivilegedDatabaseDto database, Principal principal, String role)
+            throws NotAllowedException {
+        if (database.getIsSchemaPublic()) {
+            log.trace("database with id {} has public schema: no access needed", database.getId());
+            return;
+        }
+        log.trace("database with id {} has private schema", database.getId());
+        if (principal == null) {
+            log.error("Access not allowed: no authorization provided");
+            throw new NotAllowedException("Access not allowed: no authorization provided");
+        }
+        log.trace("principal: {}", principal.getName());
+        if (!hasRole(principal, role)) {
+            log.error("Access not allowed: role {} missing", role);
+            throw new NotAllowedException("Access not allowed: role " + role + " missing");
+        }
+        log.trace("principal has role '{}': access granted", role);
+    }
+
+    public void validateOnlyAccess(PrivilegedDatabaseDto database, Principal principal, boolean writeAccessOnly)
+            throws NotAllowedException, RemoteUnavailableException, MetadataServiceException {
+        if (principal == null) {
+            throw new NotAllowedException("No principal provided");
+        }
+        if (isSystem(principal)) {
+            return;
+        }
+        final DatabaseAccessDto access = metadataServiceGateway.getAccess(database.getId(), getId(principal));
+        log.trace("found access: {}", access);
+        if (writeAccessOnly && !(access.getType().equals(AccessTypeDto.WRITE_OWN) || access.getType().equals(AccessTypeDto.WRITE_ALL))) {
+            log.error("Access not allowed: no write access");
+            throw new NotAllowedException("Access not allowed: no write access");
+        }
+    }
+
     public void validateForbiddenStatements(String query) throws QueryNotSupportedException {
         final List<String> words = new LinkedList<>();
         Arrays.stream(queryConfig.getForbiddenKeywords())
diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java
index 4d688393cb8b0aa3054bea77c9ad0cc534aa5e65..fb244bb30113ab90f4f2209bf4e80b5d11cd9185 100644
--- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java
+++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java
@@ -90,13 +90,14 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs
     @Override
     public Dataset<Row> getData(PrivilegedDatabaseDto database, QueryDto subset, Long page, Long size)
             throws ViewMalformedException, SQLException, QueryMalformedException, TableNotFoundException {
-        if (!viewService.existsByName(database, metadataMapper.queryDtoToViewName(subset))) {
-            log.warn("Missing internal view {} for subset with id {}: create it from subset query", metadataMapper.queryDtoToViewName(subset), subset.getId());
+        final String viewName = metadataMapper.queryDtoToViewName(subset);
+        if (!viewService.existsByName(database, viewName)) {
+            log.warn("Missing internal view {} for subset with id {}: create it from subset query", viewName, subset.getId());
             viewService.create(database, subset);
         } else {
-            log.debug("internal view {} for subset with id {} exists", metadataMapper.queryDtoToViewName(subset), subset.getId());
+            log.debug("internal view {}.{} for subset with id {} exists", database.getInternalName(), viewName, subset.getId());
         }
-        return tableService.getData(database, metadataMapper.queryDtoToViewName(subset), subset.getExecution(), page, size, null, null);
+        return tableService.getData(database, viewName, subset.getExecution(), page, size, null, null);
     }
 
     @Override
diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
index 21a1659923c89b1a054c34fd89f4351ba1798417..6ed865ca25923bea14c9ed39c3d8b18bacbc7f63 100644
--- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
+++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java
@@ -203,7 +203,7 @@ public class TableEndpoint extends AbstractEndpoint {
                 principal.getName());
         final Database database = databaseService.findById(databaseId);
         final Table table = tableService.findById(database, tableId);
-        if (!table.getOwner().getId().equals(getId(principal))) {
+        if (!table.getOwner().getId().equals(getId(principal)) && !isSystem(principal)) {
             log.error("Failed to update table statistics: not owner");
             throw new NotAllowedException("Failed to update table statistics: not owner");
         }
@@ -480,16 +480,24 @@ public class TableEndpoint extends AbstractEndpoint {
             UserNotFoundException, NotAllowedException, AccessNotFoundException {
         log.debug("endpoint find table, databaseId={}, tableId={}", databaseId, tableId);
         final Database database = databaseService.findById(databaseId);
-        endpointValidator.validateOnlyPrivateDataAccess(database, principal);
-        endpointValidator.validateOnlyPrivateDataHasRole(database, principal, "find-table");
         final Table table = tableService.findById(database, tableId);
         boolean isOwner = false;
         if (principal != null) {
             isOwner = table.getOwner().getId().equals(getId(principal));
-            try {
-                accessService.find(table.getDatabase(), userService.findById(getId(principal)));
-            } catch (UserNotFoundException | AccessNotFoundException e) {
-                /* ignore */
+            if (table.getIsSchemaPublic()) {
+                try {
+                    accessService.find(table.getDatabase(), userService.findById(getId(principal)));
+                } catch (UserNotFoundException | AccessNotFoundException e) {
+                    if (!isOwner && !isSystem(principal)) {
+                        log.error("Failed to find table with id {}: private and not authorized", table);
+                        throw new NotAllowedException("Failed to find table with id " + tableId + ": private and not authorized");
+                    }
+                }
+            }
+        } else {
+            if (!table.getIsSchemaPublic()) {
+                log.error("Failed to find table with id {}: private and not authorized", table);
+                throw new NotAllowedException("Failed to find table with id " + tableId + ": private and not authorized");
             }
         }
         if (!table.getIsSchemaPublic() && !isOwner && !isSystem(principal)) {
diff --git a/dbrepo-search-service/Pipfile.lock b/dbrepo-search-service/Pipfile.lock
index 92168e4d68c3931a1c84e22912f2fa3784d0bad2..9a33916adfdf47848c302c03e7110bba5dc8d09c 100644
--- a/dbrepo-search-service/Pipfile.lock
+++ b/dbrepo-search-service/Pipfile.lock
@@ -360,7 +360,7 @@
         },
         "dbrepo": {
             "hashes": [
-                "sha256:79ae690b125dcc6ba7ada28f07af47bca6e943ae3079232b3b4ae2d369095bb5"
+                "sha256:7cddcbdcb3eade84f67db01fa32e0649ecc01d4c3cc5e7542d3c402ad52efc19"
             ],
             "path": "./lib/dbrepo-1.6.1.tar.gz"
         },
diff --git a/dbrepo-search-service/init/Pipfile.lock b/dbrepo-search-service/init/Pipfile.lock
index 79939b60d160089bf3f51116242e508b7712b3f7..93c8b4291603d19deb3fbc79bd23e8d55e8379e4 100644
--- a/dbrepo-search-service/init/Pipfile.lock
+++ b/dbrepo-search-service/init/Pipfile.lock
@@ -254,7 +254,7 @@
         },
         "dbrepo": {
             "hashes": [
-                "sha256:79ae690b125dcc6ba7ada28f07af47bca6e943ae3079232b3b4ae2d369095bb5"
+                "sha256:7cddcbdcb3eade84f67db01fa32e0649ecc01d4c3cc5e7542d3c402ad52efc19"
             ],
             "path": "./lib/dbrepo-1.6.1.tar.gz"
         },
diff --git a/dbrepo-search-service/init/database.json b/dbrepo-search-service/init/database.json
index fb175700c614599daab44fa0fb77e51a05151b62..363624ff059daa02d4edb58f3f6bce1b5b4664dd 100644
--- a/dbrepo-search-service/init/database.json
+++ b/dbrepo-search-service/init/database.json
@@ -244,7 +244,7 @@
       "created": {
         "type": "date"
       },
-      "creator": {
+      "owner": {
         "properties": {
           "id": {
             "type": "text",
@@ -310,7 +310,7 @@
           "created": {
             "type": "date"
           },
-          "creator": {
+          "owner": {
             "properties": {
               "id": {
                 "type": "text",
@@ -359,9 +359,9 @@
               }
             }
           },
-          "creators": {
+          "owners": {
             "properties": {
-              "creator_name": {
+              "owner_name": {
                 "type": "text",
                 "fields": {
                   "keyword": {
@@ -610,7 +610,7 @@
               "auto_generated": {
                 "type": "boolean"
               },
-              "column_type": {
+              "type": {
                 "type": "text",
                 "fields": {
                   "keyword": {
@@ -640,9 +640,6 @@
               "is_null_allowed": {
                 "type": "boolean"
               },
-              "is_public": {
-                "type": "boolean"
-              },
               "mean": {
                 "type": "float"
               },
@@ -733,7 +730,7 @@
           "created": {
             "type": "date"
           },
-          "creator": {
+          "owner": {
             "properties": {
               "id": {
                 "type": "text",
@@ -906,7 +903,7 @@
               "auto_generated": {
                 "type": "boolean"
               },
-              "column_type": {
+              "type": {
                 "type": "text",
                 "fields": {
                   "keyword": {
@@ -950,7 +947,7 @@
           "created": {
             "type": "date"
           },
-          "creator": {
+          "owner": {
             "properties": {
               "id": {
                 "type": "text",
@@ -1010,7 +1007,7 @@
               "created": {
                 "type": "date"
               },
-              "creator": {
+              "owner": {
                 "properties": {
                   "id": {
                     "type": "text",
@@ -1059,9 +1056,9 @@
                   }
                 }
               },
-              "creators": {
+              "owners": {
                 "properties": {
-                  "creator_name": {
+                  "owner_name": {
                     "type": "text",
                     "fields": {
                       "keyword": {
diff --git a/dbrepo-search-service/init/lib/dbrepo-1.6.1.tar.gz b/dbrepo-search-service/init/lib/dbrepo-1.6.1.tar.gz
index 6e6884a28d818166eec6ded367274d57b137f4af..a1197ff6465d77567108f3389aae9d54dc9c8ab6 100644
Binary files a/dbrepo-search-service/init/lib/dbrepo-1.6.1.tar.gz and b/dbrepo-search-service/init/lib/dbrepo-1.6.1.tar.gz differ
diff --git a/dbrepo-search-service/lib/dbrepo-1.6.1.tar.gz b/dbrepo-search-service/lib/dbrepo-1.6.1.tar.gz
index 6e6884a28d818166eec6ded367274d57b137f4af..a1197ff6465d77567108f3389aae9d54dc9c8ab6 100644
Binary files a/dbrepo-search-service/lib/dbrepo-1.6.1.tar.gz and b/dbrepo-search-service/lib/dbrepo-1.6.1.tar.gz differ
diff --git a/dbrepo-ui/components/ResourceStatus.vue b/dbrepo-ui/components/ResourceStatus.vue
index 9ff310ce1b391ad40da8a1dea1af0883a9b0235a..36544b1c299ac09ba0fcf339ff2bf04d418980ee 100644
--- a/dbrepo-ui/components/ResourceStatus.vue
+++ b/dbrepo-ui/components/ResourceStatus.vue
@@ -44,6 +44,9 @@ export default {
         }
         return 'private'
       }
+      if (!this.resource.is_schema_public) {
+        return 'private'
+      }
       return 'public'
     },
     status () {
diff --git a/dbrepo-ui/components/dialogs/EditAccess.vue b/dbrepo-ui/components/dialogs/EditAccess.vue
index b3bd8dbf96454ae44bd207803fa736838bd10473..856a442592bf9b40000b9295c8b7ed9bc17f6b78 100644
--- a/dbrepo-ui/components/dialogs/EditAccess.vue
+++ b/dbrepo-ui/components/dialogs/EditAccess.vue
@@ -229,7 +229,7 @@ export default {
       const userService = useUserService()
       userService.findAll()
         .then((users) => {
-          this.users = users.filter(u => u.username !== this.database.owner.username)
+          this.users = users.filter(u => u.id !== this.database.owner.id)
         })
         .catch(({code}) => {
           const toast = useToastInstance()
diff --git a/dbrepo-ui/components/dialogs/EditTuple.vue b/dbrepo-ui/components/dialogs/EditTuple.vue
index 893504331618bed1a928b1e4ee533453cc6916f8..3290d230d1bf216bd2f212c4fbe9c683901e4b96 100644
--- a/dbrepo-ui/components/dialogs/EditTuple.vue
+++ b/dbrepo-ui/components/dialogs/EditTuple.vue
@@ -27,7 +27,7 @@
                 type="number">
                 <template
                   v-slot:append>
-                  {{ column.column_type.toUpperCase() }}
+                  {{ column.type.toUpperCase() }}
                   <NuxtLink
                     target="_blank"
                     class="ml-2"
@@ -61,7 +61,7 @@
                 type="text">
                 <template
                   v-slot:append>
-                  {{ column.column_type.toUpperCase() }}
+                  {{ column.type.toUpperCase() }}
                   <NuxtLink
                     target="_blank"
                     class="ml-2"
@@ -94,7 +94,7 @@
                 type="number">
                 <template
                   v-slot:append>
-                  {{ column.column_type.toUpperCase() }}
+                  {{ column.type.toUpperCase() }}
                   <NuxtLink
                     target="_blank"
                     class="ml-2"
@@ -126,7 +126,7 @@
                 :hint="hint(column)">
                 <template
                   v-slot:append>
-                  {{ column.column_type.toUpperCase() }}
+                  {{ column.type.toUpperCase() }}
                   <NuxtLink
                     target="_blank"
                     class="ml-2"
@@ -161,7 +161,7 @@
                 :items="isSet(column) ? column.sets : column.enums">
                 <template
                   v-slot:append>
-                  {{ column.column_type.toUpperCase() }}
+                  {{ column.type.toUpperCase() }}
                   <NuxtLink
                     target="_blank"
                     class="ml-2"
@@ -192,7 +192,7 @@
                 :clearable="!required(column)">
                 <template
                   v-slot:append>
-                  {{ column.column_type.toUpperCase() }}
+                  {{ column.type.toUpperCase() }}
                   <NuxtLink
                     target="_blank"
                     class="ml-2"
@@ -221,7 +221,7 @@
                 :hint="hint(column)">
                 <template
                   v-slot:append>
-                  {{ column.column_type.toUpperCase() }}
+                  {{ column.type.toUpperCase() }}
                   <NuxtLink
                     target="_blank"
                     class="ml-2"
@@ -313,6 +313,8 @@ export default {
       oldTuple: null,
       error: false,
       menu: false,
+      loadContainer: false,
+      container: null,
       bools: [
         { title: 'true', value: true },
         { title: 'false', value: false }
@@ -321,6 +323,7 @@ export default {
     }
   },
   mounted() {
+    this.fetchContainer()
     this.$refs.form.validate()
     this.oldTuple = Object.assign({}, this.tuple)
   },
@@ -328,11 +331,20 @@ export default {
     database () {
       return this.cacheStore.getDatabase
     },
+    table () {
+      return this.cacheStore.getTable
+    },
     columnTypes () {
-      if (!this.database) {
+      if (!this.container) {
+        return []
+      }
+      return this.container.image.data_types
+    },
+    primaryKeyColumns () {
+      if (!this.table) {
         return []
       }
-      return this.database.container.image.data_types
+      return this.table.constraints.primary_key.map(pk => pk.column)
     },
     title () {
       return (this.edit ? this.$t('toolbars.table.data.edit') : this.$t('toolbars.table.data.add')) + ' ' + this.$t('toolbars.table.data.tuple')
@@ -360,7 +372,7 @@ export default {
       if (!is_null_allowed) {
         hint += this.$t('pages.table.subpages.data.required.hint')
       }
-      if (column.column_type === 'sequence') {
+      if (column.type === 'sequence') {
         hint += ' ' + this.$t('pages.table.subpages.data.auto.hint')
       }
       if (is_primary_key) {
@@ -371,47 +383,47 @@ export default {
       }
       return hint
     },
-    documentationLink ({column_type}) {
-      const filter = this.columnTypes.filter(t => t.value === column_type)
+    documentationLink ({type}) {
+      const filter = this.columnTypes.filter(t => t.value === type)
       if (filter.length !== 1) {
         return null
       }
       return filter[0].documentation
     },
-    formatHint ({column_type}) {
-      const filter = this.columnTypes.filter(t => t.value === column_type)
+    formatHint ({type}) {
+      const filter = this.columnTypes.filter(t => t.value === type)
       if (filter.length !== 1) {
         return null
       }
       return filter[0].data_hint
     },
     isTextField (column) {
-      const { column_type } = column
-      return ['char', 'varchar', 'tinytext', 'mediumtext'].includes(column_type)
+      const { type } = column
+      return ['char', 'varchar', 'tinytext', 'mediumtext'].includes(type)
     },
     isTextArea (column) {
-      return ['text'].includes(column.column_type)
+      return ['text'].includes(column.type)
     },
     isFileField (column) {
-      return ['blob', 'longblob', 'mediumblob', 'tinyblob'].includes(column.column_type)
+      return ['blob', 'longblob', 'mediumblob', 'tinyblob'].includes(column.type)
     },
     isBoolean (column) {
-      return ['bool'].includes(column.column_type)
+      return ['bool'].includes(column.type)
     },
     isNumber (column) {
-      return ['int', 'binary', 'bit', 'tinyint', 'smallint', 'mediumint', 'bigint'].includes(column.column_type)
+      return ['int', 'binary', 'bit', 'tinyint', 'smallint', 'mediumint', 'bigint', 'serial'].includes(column.type)
     },
     isFloatingPoint (column) {
-      return ['float', 'double', 'decimal'].includes(column.column_type)
+      return ['float', 'double', 'decimal'].includes(column.type)
     },
     isEnum (column) {
-      return column.column_type === 'enum'
+      return column.type === 'enum'
     },
     isSet (column) {
-      return column.column_type === 'set'
+      return column.type === 'set'
     },
     isTimeField (column) {
-      return ['date', 'datetime', 'timestamp', 'time', 'year'].includes(column.column_type)
+      return ['date', 'datetime', 'timestamp', 'time', 'year'].includes(column.type)
     },
     rules (column) {
       if (column.is_null_allowed) {
@@ -419,7 +431,7 @@ export default {
       }
       const rules = []
       rules.push(v => v !== null || this.$t('validation.required'))
-      if (column.column_type === 'decimal' || column.column_type === 'double') {
+      if (column.type === 'decimal' || column.type === 'double') {
         rules.push(v => !(!v || v.split('.')[0].length > column.size) || `${this.$t('pages.table.subpages.data.float.max')} ${column.size} ${this.$t('pages.table.subpages.data.float.before')}`)
         rules.push(v => !(!v || (column.d && v.split('.')[1].length > column.d)) || `${this.$t('pages.table.subpages.data.float.max')} ${column.d} ${this.$t('pages.table.subpages.data.float.after')}`)
       }
@@ -439,19 +451,11 @@ export default {
     },
     updateTuple () {
       const constraints = {}
-      if (this.table.constraints.primary_key.length > 0) {
-        this.table.constraints.primary_key
-          .forEach((pk) => {
-            constraints[pk.column.internal_name] = this.oldTuple[pk.column.internal_name]
-          })
-        console.debug('table has primary key: set update tuple constraints', constraints)
-      } else {
-        this.table.columns
-          .forEach((column) => {
-            constraints[column.internal_name] = this.oldTuple[column.internal_name]
-          })
-        console.debug('table does not have a primary key: set update tuple constraints', constraints)
-      }
+      this.primaryKeyColumns
+        .forEach((pk) => {
+          constraints[pk.internal_name] = this.oldTuple[pk.internal_name]
+        })
+      console.debug('table has primary key: set update tuple constraints', constraints)
       const tupleService = useTupleService()
       this.loading = true
       tupleService.update(this.$route.params.database_id, this.$route.params.table_id, { data: this.tuple, keys: constraints })
@@ -494,7 +498,7 @@ export default {
           this.$emit('close', { success: true })
           this.loading = false
         })
-        .catch(({message}) => {
+        .catch(({code, message}) => {
           this.loading = false
           const toast = useToastInstance()
           if (typeof code !== 'string') {
@@ -510,6 +514,29 @@ export default {
       const toast = useToastInstance()
       toast.success(this.$t('success.upload.blob'))
       this.tuple[column.internal_name] = s3key
+    },
+    fetchContainer () {
+      if (!this.database) {
+        return
+      }
+      this.loadContainer = true
+      const containerService = useContainerService()
+      containerService.findOne(this.database.container.id)
+        .then((container) => {
+          this.container = container
+          this.loadContainer = false
+        })
+        .catch(({code, message}) => {
+          this.loadContainer = false
+          const toast = useToastInstance()
+          if (typeof code !== 'string') {
+            return
+          }
+          toast.error(message)
+        })
+        .finally(() => {
+          this.loadContainer = false
+        })
     }
   }
 }
diff --git a/dbrepo-ui/components/search/AdvancedSearch.vue b/dbrepo-ui/components/search/AdvancedSearch.vue
index 13de402e019f2a791f0233c4fc611164e46e20d8..b312b2dc5291812fd98930e8585b50e389c341c5 100644
--- a/dbrepo-ui/components/search/AdvancedSearch.vue
+++ b/dbrepo-ui/components/search/AdvancedSearch.vue
@@ -75,14 +75,14 @@
                 :variant="inputVariant"
                 :label="field.attr_friendly_name" />
               <v-text-field
-                v-if="(field.type === 'keyword' && field.attr_name !== 'column_type') || field.type === 'text' || field.type === 'date'"
+                v-if="(field.type === 'keyword' && field.attr_name !== 'type') || field.type === 'text' || field.type === 'date'"
                 v-model="advancedSearchData[field.attr_name]"
                 type="text"
                 :variant="inputVariant"
                 :label="field.attr_friendly_name"
                 clearable />
               <v-select
-                v-if="field.type === 'keyword' && field.attr_name === 'column_type'"
+                v-if="field.type === 'keyword' && field.attr_name === 'type'"
                 v-model="advancedSearchData[field.attr_name]"
                 :items="columnTypes"
                 item-value="value"
diff --git a/dbrepo-ui/components/subset/Results.vue b/dbrepo-ui/components/subset/Results.vue
index e9e3c0deb5cddf119cab770014ff085d71b6df94..e558186daf15c186dfdc085a844799815e3756a4 100644
--- a/dbrepo-ui/components/subset/Results.vue
+++ b/dbrepo-ui/components/subset/Results.vue
@@ -2,12 +2,15 @@
   <div>
     <v-data-table-server
       flat
+      v-model="selection"
       :headers="headers"
       :loading="loading || loadingCount || loadingExecute"
       :options="options"
       :items="result.rows"
       :items-length="total"
       :footer-props="footerProps"
+      :show-select="select"
+      return-object
       :items-per-page-options="footerProps.itemsPerPageOptions"
       @update:options="updateOptions" />
   </div>
@@ -24,6 +27,10 @@ export default {
       type: Boolean,
       default: () => false
     },
+    select: {
+      type: Boolean,
+      default: () => false
+    },
     timestamp: {
       type: String,
       default: () => new Date().toISOString()
@@ -35,6 +42,7 @@ export default {
       loadingExecute: false,
       resultId: null,
       id: null,
+      selection: null,
       result: {
         headers: [],
         rows: []
@@ -64,6 +72,11 @@ export default {
         this.reExecute(this.id)
       },
       deep: true
+    },
+    selection: {
+      handler () {
+        this.$emit('selection', this.selection)
+      }
     }
   },
   methods: {
@@ -224,6 +237,9 @@ export default {
       this.options.page = page
       this.options.itemsPerPage = itemsPerPage
       this.reExecute(this.id)
+    },
+    resetSelection () {
+      this.selection = []
     }
   }
 }
diff --git a/dbrepo-ui/components/subset/SubsetList.vue b/dbrepo-ui/components/subset/SubsetList.vue
index ae8f2bf448753d4d7b6b4454f0b7e1671d7eb25d..6d84a6a48ea91bbbbbf7ef6fa869c2230ecbcd29 100644
--- a/dbrepo-ui/components/subset/SubsetList.vue
+++ b/dbrepo-ui/components/subset/SubsetList.vue
@@ -88,7 +88,11 @@ export default {
       queryService.findAll(this.$route.params.database_id, true)
         .then((subsets) => {
           this.loadingSubsets = false
-          this.subsets = subsets
+          this.subsets = subsets.map(subset => {
+            subset.is_public = this.database.is_public
+            subset.is_schema_public = this.database.is_schema_public
+            return subset
+          })
         })
         .catch(({code}) => {
           this.loadingSubsets = false
diff --git a/dbrepo-ui/components/table/TableList.vue b/dbrepo-ui/components/table/TableList.vue
index 2cb42a1633e47ca9d117e4449318582666975d35..234470076db8bb400542fdebe4274b2eefad8e62 100644
--- a/dbrepo-ui/components/table/TableList.vue
+++ b/dbrepo-ui/components/table/TableList.vue
@@ -56,7 +56,7 @@ export default {
       dialogDelete: false,
       headers: [
         { value: 'name', title: 'Name' },
-        { value: 'column_type', title: 'Type' },
+        { value: 'type', title: 'Type' },
         { value: 'column_concept', title: 'Concept' },
         { value: 'column_unit', title: 'Unit' },
         { value: 'is_primary_key', title: 'Primary Key' },
diff --git a/dbrepo-ui/composables/container-service.ts b/dbrepo-ui/composables/container-service.ts
index 9aaf116e7efeed12f03170ec22340119d7d3341f..f1280517566165b8f190b9bd05eea3b1d4ac98f1 100644
--- a/dbrepo-ui/composables/container-service.ts
+++ b/dbrepo-ui/composables/container-service.ts
@@ -17,5 +17,21 @@ export const useContainerService = (): any => {
     })
   }
 
-  return {findAll}
+  async function findOne(containerId: number): Promise<ContainerDto> {
+    const axios = useAxiosInstance();
+    console.debug('find containers');
+    return new Promise<ContainerDto>((resolve, reject) => {
+      axios.get<ContainerDto>(`/api/container/${containerId}`)
+        .then((response) => {
+          console.info(`Find container with id ${containerId}`)
+          resolve(response.data)
+        })
+        .catch((error) => {
+          console.error('Failed to find container', error)
+          reject(axiosErrorToApiError(error))
+        })
+    })
+  }
+
+  return {findAll, findOne}
 }
diff --git a/dbrepo-ui/composables/table-service.ts b/dbrepo-ui/composables/table-service.ts
index f2e7a3a2f9f07cf9ed25b1220190e22879ef7a83..4abaebec937ecdfc2f965a8184b2982ef776ddce 100644
--- a/dbrepo-ui/composables/table-service.ts
+++ b/dbrepo-ui/composables/table-service.ts
@@ -287,6 +287,7 @@ export const useTableService = (): any => {
     exportData,
     create,
     remove,
+    updateSemantics,
     removeTuple,
     history,
     suggest,
diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json
index 737e880820923f810ba04c91b82e9be349056e76..c4d5ef5eba6f9d6592ef2f47c86644530d82c1e7 100644
--- a/dbrepo-ui/locales/en-US.json
+++ b/dbrepo-ui/locales/en-US.json
@@ -1099,16 +1099,16 @@
   },
   "error": {
     "permission": {
-      "title": "You do not have permission to view this resource",
-      "text": "This a default-fallback error message since this resource marked as private. Please try logging in or request read-access permissions from the owner."
+      "title": "You do not have permission to view this {resource}",
+      "text": "This a default-fallback error message since this {resource} marked as private. Please try logging in or request read-access permissions from the owner."
     },
     "missing": {
-      "title": "The requested resource was not found",
-      "text": "We could not find the requested resource anywhere."
+      "title": "The requested {resource} was not found",
+      "text": "We could not find the requested {resource} anywhere."
     },
     "gone": {
-      "title": "The requested resource does not exist anymore",
-      "text": "We could not find the requested resource anymore."
+      "title": "The requested {resource} does not exist anymore",
+      "text": "We could not find the requested {resource} anymore."
     },
     "auth": {
       "connection": "Failed to contact auth service",
diff --git a/dbrepo-ui/pages/database/[database_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/info.vue
index 840e31bf505ff86a3b0df23dfaab7df2eb138ef7..4d88b2c6c86dfdb86e9e61c7a601212e0c7293ad 100644
--- a/dbrepo-ui/pages/database/[database_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/info.vue
@@ -1,5 +1,6 @@
 <template>
-  <div>
+  <div
+    v-if="canViewSchema">
     <DatabaseToolbar />
     <v-window
       v-model="tab">
@@ -160,6 +161,12 @@
       :items="items"
       class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'database' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'database' })" />
+  <pre v-if="error">{{ error }}</pre>
 </template>
 
 <script>
@@ -168,7 +175,7 @@ import Summary from '@/components/identifier/Summary.vue'
 import Select from '@/components/identifier/Select.vue'
 import UserBadge from '@/components/user/UserBadge.vue'
 import JumboBox from '@/components/JumboBox.vue'
-import { sizeToHumanLabel } from '@/utils'
+import { sizeToHumanLabel, errorCodeKey } from '@/utils'
 import { useUserStore } from '@/stores/user'
 import { useCacheStore } from '@/stores/cache'
 
@@ -198,7 +205,6 @@ export default {
       useServerSeoMeta(identifierService.databaseToServerSeoMeta(data.value))
     }
     return {
-      database: data,
       error
     }
   },
@@ -356,7 +362,16 @@ export default {
         return null
       }
       return this.database.preview_image
+    },
+    canViewSchema () {
+      if (this.error) {
+        return false
+      }
+      return this.database
     }
+  },
+  methods: {
+    errorCodeKey
   }
 }
 </script>
diff --git a/dbrepo-ui/pages/database/[database_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/settings.vue
index 0e833914a266810967e41f40f9c13da46fef2953..5e52a9081603653da7847b7029f077761219870a 100644
--- a/dbrepo-ui/pages/database/[database_id]/settings.vue
+++ b/dbrepo-ui/pages/database/[database_id]/settings.vue
@@ -1,5 +1,6 @@
 <template>
-  <div>
+  <div
+    v-if="canView">
     <DatabaseToolbar
       ref="toolbar" />
     <v-window
@@ -241,6 +242,11 @@
     </v-window>
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'database' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'database' })" />
 </template>
 
 <script>
@@ -248,12 +254,30 @@ import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue'
 import EditAccess from '@/components/dialogs/EditAccess.vue'
 import { useUserStore } from '@/stores/user'
 import { useCacheStore } from '@/stores/cache'
+import { errorCodeKey } from '@/utils'
 
 export default {
   components: {
     DatabaseToolbar,
     EditAccess
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id } = useRoute().params
+    const { error } = useFetch(`${config.public.api.server}/api/database/${database_id}`, {
+      immediate: true,
+      method: 'HEAD',
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    return {
+      error
+    }
+  },
   data () {
     return {
       dialogDelete: false,
@@ -405,6 +429,12 @@ export default {
       }
       return this.roles.includes('modify-database-image')
     },
+    canView () {
+      if (this.error) {
+        return false
+      }
+      return this.database
+    },
     previewImage () {
       if (this.file) {
         return URL.createObjectURL(this.file)
@@ -456,6 +486,7 @@ export default {
     this.modifyOwner.id = this.database.owner.id
   },
   methods: {
+    errorCodeKey,
     submit () {
       this.$refs.form.validate()
     },
diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue
index 33f370e2f275371bb72b34eed23adda2b4085181..88add5804efd050bca3c9cd73011724832d3d3cd 100644
--- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue
@@ -38,8 +38,13 @@
             :title="$t('pages.subset.visibility.title')"
             density="compact">
             <ResourceStatus
+              v-if="!identifier"
               :inline="true"
-              :resource="subset" />
+              :resource="database" />
+            <ResourceStatus
+              v-else
+              :inline="true"
+              :resource="identifier" />
           </v-list-item>
           <v-list-item
             v-if="subset.creator"
diff --git a/dbrepo-ui/pages/database/[database_id]/subset/index.vue b/dbrepo-ui/pages/database/[database_id]/subset/index.vue
index 8d454ce83d11a4f6143093ecb20d268b1897a9d2..7866e6d5de310d95729268dc3c1f264be20e540b 100644
--- a/dbrepo-ui/pages/database/[database_id]/subset/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/subset/index.vue
@@ -1,18 +1,43 @@
 <template>
-  <div>
+  <div
+    v-if="canViewSchema">
     <DatabaseToolbar />
     <SubsetList />
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'subset' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'subset' })" />
 </template>
 
 <script>
 import SubsetList from '@/components/subset/SubsetList.vue'
+import { errorCodeKey } from '@/utils'
+import { useCacheStore } from '@/stores/cache'
 
 export default {
   components: {
     SubsetList
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id } = useRoute().params
+    const { error } = useFetch(`${config.public.api.server}/api/database/${database_id}`, {
+      immediate: true,
+      method: 'HEAD',
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    return {
+      error
+    }
+  },
   data () {
     return {
       items: [
@@ -29,8 +54,23 @@ export default {
           to: `/database/${this.$route.params.database_id}/subset`,
           disabled: true
         }
-      ]
+      ],
+      cacheStore: useCacheStore()
     }
+  },
+  computed: {
+    database () {
+      return this.cacheStore.getDatabase
+    },
+    canViewSchema () {
+      if (this.error) {
+        return false
+      }
+      return this.database
+    }
+  },
+  methods: {
+    errorCodeKey
   }
 }
 </script>
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue
index fc4b046df8226fa3e14eaac3a4b9af94e4f2aced..6d84d8a6d63049aa2c5e6ef07edf7771800a01df 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue
@@ -9,14 +9,14 @@
       <v-spacer />
       <v-btn
         v-if="canAddTuple"
-        :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-plus' : null"
+        :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-plus' : null"
         variant="flat"
         :text="$t('toolbars.table.data.add')"
         class="ml-2"
         @click="addTuple" />
       <v-btn
         v-if="canEditTuple"
-        :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-pencil' : null"
+        :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-pencil' : null"
         color="warning"
         variant="flat"
         :text="$t('toolbars.table.data.edit')"
@@ -24,7 +24,7 @@
         @click="editTuple" />
       <v-btn
         v-if="canDeleteTuple"
-        :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-delete' : null"
+        :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-delete' : null"
         color="error"
         variant="flat"
         :text="$t('toolbars.table.data.delete')"
@@ -32,14 +32,14 @@
         :loading="loadingDelete"
         @click="deleteItems" />
       <v-btn
-        :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-download' : null"
+        :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-download' : null"
         variant="flat"
         :loading="downloadLoading"
         :text="$t('toolbars.table.data.download')"
         class="ml-2"
         @click.stop="download" />
       <v-btn
-        :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-refresh' : null"
+        :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-refresh' : null"
         variant="flat"
         :text="$t('toolbars.table.data.refresh')"
         class="ml-2"
@@ -47,7 +47,7 @@
         :loading="loadingData"
         @click="reload" />
       <v-btn
-        :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-update' : null"
+        :prepend-icon="$vuetify.display.mdAndUp ? 'mdi-update' : null"
         variant="flat"
         :text="$t('toolbars.table.data.version')"
         class="ml-2 mr-2"
@@ -61,13 +61,16 @@
         {{ $t('error.table.connection') }}
       </v-card-text>
     </v-card>
-    <v-card tile>
+    <v-card
+      tile>
       <QueryResults
         id="query-results"
         ref="queryResults"
+        class="mt-0 mb-0"
         type="table"
+        :select="canSelectTuples"
         :timestamp="versionISO || lastReload.toISOString()"
-        class="mt-0 mb-0" />
+        @selection="updateSelect" />
     </v-card>
     <v-dialog
       v-model="pickVersionDialog"
@@ -99,18 +102,24 @@
     </v-dialog>
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'table' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'table' })" />
 </template>
 
 <script>
 import TableHistory from '@/components/table/TableHistory.vue'
 import TimeDrift from '@/components/TimeDrift.vue'
 import TableToolbar from '@/components/table/TableToolbar.vue'
-import { formatTimestamp } from '@/utils'
+import { errorCodeKey, formatTimestamp } from '@/utils'
 import { useUserStore } from '@/stores/user'
 import { useCacheStore } from '@/stores/cache'
 import EditTuple from '@/components/dialogs/EditTuple.vue'
 import BlobDownload from '@/components/table/BlobDownload.vue'
 import QueryResults from '@/components/subset/Results.vue'
+import JumboBox from '@/components/JumboBox.vue'
 
 export default {
   components: {
@@ -119,7 +128,29 @@ export default {
     EditTuple,
     TableHistory,
     TableToolbar,
-    TimeDrift
+    TimeDrift,
+    JumboBox
+  },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id, table_id } = useRoute().params
+    const { error, data } = useFetch(`${config.public.api.server}/api/database/${database_id}/table/${table_id}`, {
+      immediate: true,
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    if (data.value) {
+      const identifierService = useIdentifierService()
+      useServerHead(identifierService.databaseToServerHead(data.value))
+      useServerSeoMeta(identifierService.databaseToServerSeoMeta(data.value))
+    }
+    return {
+      error
+    }
   },
   data () {
     return {
@@ -144,7 +175,6 @@ export default {
       version: null,
       lastReload: new Date(),
       tab: null,
-      error: false,
       tuple: null,
       options: {
         page: 1,
@@ -193,9 +223,6 @@ export default {
     user () {
       return this.userStore.getUser
     },
-    tables () {
-      return this.cacheStore.getTable
-    },
     access () {
       return this.userStore.getAccess
     },
@@ -238,8 +265,16 @@ export default {
       }
       return this.access.type === 'write_all'
     },
+    primaryKeyColumns () {
+      if (!this.table) {
+        return []
+      }
+      return this.table.constraints.primary_key.map(pk => pk.column)
+    },
     canViewTableData () {
-      /* view when database is public or when private: 1) view-table-data role present 2) access is at least read */
+      if (this.error) {
+        return false
+      }
       if (!this.table) {
         return false
       }
@@ -258,6 +293,13 @@ export default {
       const userService = useUserService()
       return userService.hasWriteAccess(this.table, this.access, this.user) && this.roles.includes('insert-table-data')
     },
+    canSelectTuples () {
+      if (!this.roles) {
+        return false
+      }
+      const userService = useUserService()
+      return userService.hasWriteAccess(this.table, this.access, this.user) && this.roles.includes('insert-table-data')
+    },
     canEditTuple () {
       if (!this.roles || this.selection === null || this.selection.length !== 1) {
         return false
@@ -282,6 +324,7 @@ export default {
     this.reload()
   },
   methods: {
+    errorCodeKey,
     addTuple () {
       this.tuple = {}
       this.columns.forEach((c) => {
@@ -299,8 +342,7 @@ export default {
       for (const select of this.selection) {
         /* remove in container */
         const constraints = {}
-        this.columns
-          .filter(c => c.is_primary_key)
+        this.primaryKeyColumns
           .forEach((c) => {
             constraints[c.internal_name] = select[c.internal_name]
           })
@@ -327,6 +369,7 @@ export default {
           toast.success(`Deleted ${this.selection.length} row(s)`)
           this.$emit('modified', { success: true, action: 'delete' })
           this.selection = []
+          this.$refs.queryResults.resetSelection()
           this.reload()
         })
       this.loadingDelete = false
@@ -399,11 +442,14 @@ export default {
     },
     reload () {
       this.lastReload = new Date()
+      if (!this.canViewTableData) {
+        return
+      }
       this.$refs.queryResults.reExecute(Number(this.$route.params.table_id))
       this.$refs.queryResults.reExecuteCount(Number(this.$route.params.table_id))
     },
     isFileField (column) {
-      return ['blob', 'longblob', 'mediumblob', 'tinyblob'].includes(column.column_type)
+      return ['blob', 'longblob', 'mediumblob', 'tinyblob'].includes(column.type)
     },
     close ({ success }) {
       console.debug('closed edit/create tuple dialog')
@@ -412,7 +458,11 @@ export default {
       if (success) {
         this.reload()
         this.selection = []
+        this.$refs.queryResults.resetSelection()
       }
+    },
+    updateSelect (selection) {
+      this.selection = selection
     }
   }
 }
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue
index 5c258e75eccb654d2a3ecbd9bdb50151ee95d1a3..93c16d67925ed584e0427a14f5fd53ede7c0826d 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue
@@ -1,6 +1,6 @@
 <template>
   <div
-    v-if="table">
+    v-if="canViewSchema">
     <TableToolbar
       :selection="selection" />
     <v-card
@@ -116,6 +116,11 @@
     </v-card>
     <v-breadcrumbs :items="items" class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'table' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'table' })" />
 </template>
 
 <script>
@@ -125,6 +130,7 @@ import Summary from '@/components/identifier/Summary.vue'
 import UserBadge from '@/components/user/UserBadge.vue'
 import { useUserStore } from '@/stores/user'
 import { useCacheStore } from '@/stores/cache'
+import { errorCodeKey } from '@/utils'
 
 export default {
   components: {
@@ -151,7 +157,6 @@ export default {
       useServerSeoMeta(identifierService.databaseToServerSeoMeta(data.value))
     }
     return {
-      table: data,
       error
     }
   },
@@ -201,6 +206,9 @@ export default {
     database () {
       return this.cacheStore.getDatabase
     },
+    table () {
+      return this.cacheStore.getTable
+    },
     roles () {
       return this.userStore.getRoles
     },
@@ -213,6 +221,21 @@ export default {
       }
       return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all'
     },
+    canViewSchema () {
+      if (this.error) {
+        return false
+      }
+      if (!this.table) {
+        return false
+      }
+      if (this.table.is_schema_public || this.table.is_public) {
+        return true
+      }
+      if (!this.user) {
+        return false
+      }
+      return this.hasReadAccess || this.table.owner.id === this.user.id || this.database.owner.id === this.user.id
+    },
     canWrite () {
       if (!this.table || !this.user || !this.access) {
         return false
@@ -288,6 +311,9 @@ export default {
         return this.$t('pages.table.connection.permissions.read')
       }
     }
-  }
+  },
+  methods: {
+    errorCodeKey
+  },
 }
 </script>
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue
index 5364b6c8997e4e336852f02747f2b7bfc6b08ee7..e0d4f9555fc780b7bfcad2275b71261861eb1b3e 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue
@@ -118,17 +118,44 @@
       :items="items"
       class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'table' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'table' })" />
 </template>
 
 <script>
 import TableToolbar from '@/components/table/TableToolbar.vue'
 import { useUserStore } from '@/stores/user'
 import { useCacheStore } from '@/stores/cache'
+import { errorCodeKey } from '@/utils'
 
 export default {
   components: {
     TableToolbar
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id, table_id } = useRoute().params
+    const { error, data } = useFetch(`${config.public.api.server}/api/database/${database_id}/table/${table_id}`, {
+      immediate: true,
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    if (data.value) {
+      const identifierService = useIdentifierService()
+      useServerHead(identifierService.databaseToServerHead(data.value))
+      useServerSeoMeta(identifierService.databaseToServerSeoMeta(data.value))
+    }
+    return {
+      error
+    }
+  },
   data () {
     return {
       selection: [],
@@ -160,7 +187,7 @@ export default {
       ],
       headers: [
         { value: 'internal_name', title: this.$t('pages.table.subpages.schema.internal-name.title') },
-        { value: 'column_type', title: this.$t('pages.table.subpages.schema.column-type.title') },
+        { value: 'type', title: this.$t('pages.table.subpages.schema.column-type.title') },
         { value: 'extra', title: this.$t('pages.table.subpages.schema.extra.title') },
         { value: 'column_concept', title: this.$t('pages.table.subpages.schema.concept.title') },
         { value: 'column_unit', title: this.$t('pages.table.subpages.schema.unit.title') },
@@ -195,6 +222,9 @@ export default {
       return this.userStore.getRoles
     },
     canViewSchema () {
+      if (this.error) {
+        return false
+      }
       if (!this.table) {
         return false
       }
@@ -204,7 +234,7 @@ export default {
       if (!this.user) {
         return false
       }
-      return this.hasReadAccess || this.table.owned_by === this.user.id || this.database.owner.id === this.user.id
+      return this.hasReadAccess || this.table.owner.id === this.user.id || this.database.owner.id === this.user.id
     },
     primaryKeysColumns () {
       return this.table.constraints.primary_key.map(pk => pk.column.internal_name).join(', ')
@@ -235,10 +265,11 @@ export default {
     }
   },
   methods: {
+    errorCodeKey,
     extra (column) {
-      if (column.column_type === 'float') {
+      if (column.type === 'float') {
         return `precision=${column.size}`
-      } else if (['decimal', 'double'].includes(column.column_type)) {
+      } else if (['decimal', 'double'].includes(column.type)) {
         let extra = ''
         if (column.size !== null) {
           extra += `size=${column.size}`
@@ -250,11 +281,11 @@ export default {
           extra += `d=${column.d}`
         }
         return extra
-      } else if (column.column_type === 'enum') {
+      } else if (column.type === 'enum') {
         return `(${column.enums.join(', ')})`
-      } else if (column.column_type === 'set') {
+      } else if (column.type === 'set') {
         return `(${column.sets.join(', ')})`
-      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.column_type)) {
+      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.type)) {
         return column.size !== null ? `size=${column.size}` : ''
       }
       return null
diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue
index 936db0ab73e70d9ca11d4bd0a8ee80c5618a8fe9..45a8a3a5f2d701801f9ca1fb6f6f7e2055a04e1e 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue
@@ -167,7 +167,7 @@ export default {
       ],
       headers: [
         { value: 'internal_name', title: this.$t('pages.table.subpages.schema.internal-name.title') },
-        { value: 'column_type', title: this.$t('pages.table.subpages.schema.column-type.title') },
+        { value: 'type', title: this.$t('pages.table.subpages.schema.column-type.title') },
         { value: 'extra', title: this.$t('pages.table.subpages.schema.extra.title') },
         { value: 'column_concept', title: this.$t('pages.table.subpages.schema.concept.title') },
         { value: 'column_unit', title: this.$t('pages.table.subpages.schema.unit.title') },
@@ -258,9 +258,9 @@ export default {
       this.$refs.form.validate()
     },
     extra (column) {
-      if (column.column_type === 'float') {
+      if (column.type === 'float') {
         return `precision=${column.size}`
-      } else if (['decimal', 'double'].includes(column.column_type)) {
+      } else if (['decimal', 'double'].includes(column.type)) {
         let extra = ''
         if (column.size !== null) {
           extra += `size=${column.size}`
@@ -272,11 +272,11 @@ export default {
           extra += `d=${column.d}`
         }
         return extra
-      } else if (column.column_type === 'enum') {
+      } else if (column.type === 'enum') {
         return `(${column.enums.join(', ')})`
-      } else if (column.column_type === 'set') {
+      } else if (column.type === 'set') {
         return `(${column.sets.join(', ')})`
-      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.column_type)) {
+      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.type)) {
         return column.size !== null ? `size=${column.size}` : ''
       }
       return null
diff --git a/dbrepo-ui/pages/database/[database_id]/table/index.vue b/dbrepo-ui/pages/database/[database_id]/table/index.vue
index 7198c88cf31ab8203f711cd8e7982805524ecff9..9616074fddd6fa61e7033ad8de5dd9fd387e5691 100644
--- a/dbrepo-ui/pages/database/[database_id]/table/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/table/index.vue
@@ -1,15 +1,27 @@
 <template>
-  <div>
+  <div
+    v-if="canViewSchema">
     <DatabaseToolbar />
-    <v-window v-model="tab">
+    <v-window
+      v-model="tab">
       <TableList />
     </v-window>
-    <v-breadcrumbs :items="items" class="pa-0 mt-2" />
+    <v-breadcrumbs
+      :items="items"
+      class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'table' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'table' })" />
 </template>
+
 <script>
 import TableList from '@/components/table/TableList.vue'
 import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue'
+import { useCacheStore } from '@/stores/cache'
+import { errorCodeKey } from '@/utils'
 
 export default {
   name: 'Tables',
@@ -17,9 +29,26 @@ export default {
     TableList,
     DatabaseToolbar
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id } = useRoute().params
+    const { error } = useFetch(`${config.public.api.server}/api/database/${database_id}`, {
+      immediate: true,
+      method: 'HEAD',
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    return {
+      error
+    }
+  },
   data () {
     return {
-      db: null,
+      tab: 0,
       items: [
         {
           title: this.$t('navigation.databases'),
@@ -34,13 +63,23 @@ export default {
           to: `/database/${this.$route.params.database_id}/table`,
           disabled: true
         }
-      ]
+      ],
+      cacheStore: useCacheStore()
     }
   },
   computed: {
-    tab () {
-      return 1
+    database () {
+      return this.cacheStore.getDatabase
+    },
+    canViewSchema () {
+      if (this.error) {
+        return false
+      }
+      return this.database
     }
+  },
+  methods: {
+    errorCodeKey
   }
 }
 </script>
diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue
index e9559bca7a27e2b6088f1b619ac516844733a386..e126e19d9050c23b4299f9799b2895490813f33f 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/schema.vue
@@ -88,7 +88,7 @@ export default {
       ],
       headers: [
         { value: 'internal_name', title: this.$t('pages.table.subpages.schema.internal-name.title') },
-        { value: 'column_type', title: this.$t('pages.table.subpages.schema.column-type.title') },
+        { value: 'type', title: this.$t('pages.table.subpages.schema.column-type.title') },
         { value: 'extra', title: this.$t('pages.table.subpages.schema.extra.title') },
         { value: 'column_concept', title: this.$t('pages.table.subpages.schema.concept.title') },
         { value: 'column_unit', title: this.$t('pages.table.subpages.schema.unit.title') },
@@ -144,9 +144,9 @@ export default {
   },
   methods: {
     extra (column) {
-      if (column.column_type === 'float') {
+      if (column.type === 'float') {
         return `precision=${column.size}`
-      } else if (['decimal', 'double'].includes(column.column_type)) {
+      } else if (['decimal', 'double'].includes(column.type)) {
         let extra = ''
         if (column.size !== null) {
           extra += `size=${column.size}`
@@ -158,11 +158,11 @@ export default {
           extra += `d=${column.d}`
         }
         return extra
-      } else if (column.column_type === 'enum') {
+      } else if (column.type === 'enum') {
         return `(${column.enums.join(', ')})`
-      } else if (column.column_type === 'set') {
+      } else if (column.type === 'set') {
         return `(${column.sets.join(', ')})`
-      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.column_type)) {
+      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.type)) {
         return column.size !== null ? `size=${column.size}` : ''
       }
       return null
diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue
index 80c746292b7ecd6bf2904a9159d92cf534db8aea..f9c905ffd4de036c8ccd1157d14bcda1504a10a0 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/settings.vue
@@ -140,7 +140,7 @@ export default {
       ],
       headers: [
         { value: 'internal_name', title: this.$t('pages.table.subpages.schema.internal-name.title') },
-        { value: 'column_type', title: this.$t('pages.table.subpages.schema.column-type.title') },
+        { value: 'type', title: this.$t('pages.table.subpages.schema.column-type.title') },
         { value: 'extra', title: this.$t('pages.table.subpages.schema.extra.title') },
         { value: 'column_concept', title: this.$t('pages.table.subpages.schema.concept.title') },
         { value: 'column_unit', title: this.$t('pages.table.subpages.schema.unit.title') },
@@ -226,9 +226,9 @@ export default {
       this.$refs.form.validate()
     },
     extra (column) {
-      if (column.column_type === 'float') {
+      if (column.type === 'float') {
         return `precision=${column.size}`
-      } else if (['decimal', 'double'].includes(column.column_type)) {
+      } else if (['decimal', 'double'].includes(column.type)) {
         let extra = ''
         if (column.size !== null) {
           extra += `size=${column.size}`
@@ -240,11 +240,11 @@ export default {
           extra += `d=${column.d}`
         }
         return extra
-      } else if (column.column_type === 'enum') {
+      } else if (column.type === 'enum') {
         return `(${column.enums.join(', ')})`
-      } else if (column.column_type === 'set') {
+      } else if (column.type === 'set') {
         return `(${column.sets.join(', ')})`
-      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.column_type)) {
+      } else if (['int', 'char', 'varchar', 'binary', 'varbinary', 'tinyint', 'size="small"int', 'mediumint', 'bigint'].includes(column.type)) {
         return column.size !== null ? `size=${column.size}` : ''
       }
       return null
diff --git a/dbrepo-ui/pages/database/[database_id]/view/index.vue b/dbrepo-ui/pages/database/[database_id]/view/index.vue
index dc87510ae887285ac36fc647e8f2a172cb708784..5d8cee483626d54afb8dbb082d489313a30f3aad 100644
--- a/dbrepo-ui/pages/database/[database_id]/view/index.vue
+++ b/dbrepo-ui/pages/database/[database_id]/view/index.vue
@@ -1,16 +1,26 @@
 <template>
-  <div>
+  <div
+    v-if="canViewSchema">
     <DatabaseToolbar />
-    <v-window v-model="tab">
+    <v-window
+      v-model="tab">
       <ViewList />
     </v-window>
-    <v-breadcrumbs :items="items" class="pa-0 mt-2" />
+    <v-breadcrumbs
+      :items="items"
+      class="pa-0 mt-2" />
   </div>
+  <JumboBox
+    v-if="error"
+    :title="$t(errorCodeKey(error).title, { resource: 'view' })"
+    :subtitle="$t(errorCodeKey(error).subtitle)"
+    :text="$t(errorCodeKey(error).text, { resource: 'view' })" />
 </template>
 
 <script>
 import DatabaseToolbar from '@/components/database/DatabaseToolbar.vue'
 import ViewList from '@/components/view/ViewList.vue'
+import { useCacheStore } from '@/stores/cache'
 
 export default {
   name: 'Views',
@@ -18,9 +28,26 @@ export default {
     ViewList,
     DatabaseToolbar
   },
+  setup () {
+    const config = useRuntimeConfig()
+    const userStore = useUserStore()
+    const { database_id } = useRoute().params
+    const { error } = useFetch(`${config.public.api.server}/api/database/${database_id}`, {
+      immediate: true,
+      method: 'HEAD',
+      timeout: 90_000,
+      headers: {
+        Accept: 'application/json',
+        Authorization: userStore.getToken ? `Bearer ${userStore.getToken}` : null
+      }
+    })
+    return {
+      error
+    }
+  },
   data () {
     return {
-      db: null,
+      tab: 0,
       items: [
         {
           title: this.$t('navigation.databases'),
@@ -35,20 +62,23 @@ export default {
           to: `/database/${this.$route.params.database_id}/view`,
           disabled: true
         }
-      ]
+      ],
+      cacheStore: useCacheStore()
     }
   },
   computed: {
-    tab () {
-      return 1
+    database () {
+      return this.cacheStore.getDatabase
+    },
+    canViewSchema () {
+      if (this.error) {
+        return false
+      }
+      return this.database
     }
   },
-  mounted () {
-  },
   methods: {
+    errorCodeKey
   }
 }
 </script>
-
-<style scoped>
-</style>
diff --git a/dbrepo-ui/pages/search.vue b/dbrepo-ui/pages/search.vue
index c437dff5564557a1e29f1c05289e7e1a7d42ad0b..04f94a2d8e19e7cfe8747a7bc8bf5d64ad541ae0 100644
--- a/dbrepo-ui/pages/search.vue
+++ b/dbrepo-ui/pages/search.vue
@@ -230,7 +230,7 @@ export default {
         }
         return description
       } else if (this.isColumn(item)) {
-        let text = item.column_type
+        let text = item.type
         if (item.size) {
           text += `(${item.size}${item.d ? ',' + item.d : ''})`
         }
diff --git a/dbrepo-ui/utils/index.ts b/dbrepo-ui/utils/index.ts
index 4a9b2394979cd2066de446ef566071f9994d91d5..46a5e9e5550cc3860d4e1fc69ba7a010a68ff33d 100644
--- a/dbrepo-ui/utils/index.ts
+++ b/dbrepo-ui/utils/index.ts
@@ -1,6 +1,7 @@
 import {format} from 'date-fns'
 import moment from 'moment'
 import type {AxiosError} from 'axios'
+import type {H3Error} from "h3";
 
 
 export function notEmpty(str: string) {
@@ -24,6 +25,29 @@ export function notFile(files: [File[]]) {
   return files.length === 1
 }
 
+export function errorCodeKey(error: H3Error): any {
+  switch (error.statusCode) {
+    case 404:
+      return {
+        title: 'error.missing.title',
+        subtitle: 'ERR_NOT_FOUND',
+        text: 'error.missing.text'
+      }
+    case 403:
+      return {
+        title: 'error.permission.title',
+        subtitle: 'ERR_NOT_AUTHORIZED',
+        text: 'error.permission.text'
+      }
+    default:
+      return {
+        title: 'error.gone.title',
+        subtitle: 'ERR_GONE',
+        text: 'error.gone.text'
+      }
+  }
+}
+
 export function castNumberOptional(str: string): string | number {
   const num = Number(str)
   const ss = String(num)
@@ -1055,6 +1079,14 @@ export function isActiveMessage(message: any) {
 }
 
 export function axiosErrorToApiError(error: AxiosError): ApiErrorDto {
+  if (!error || !('data' in error.response)) {
+    const errorObj: ApiErrorDto = {
+      status: 'NOT_SET',
+      code: 'error.axios.connection',
+      message: ''
+    }
+    return errorObj
+  }
   if (error.code === 'ECONNABORTED') {
     /* timeout */
     const errorObj: ApiErrorDto = {
@@ -1064,15 +1096,7 @@ export function axiosErrorToApiError(error: AxiosError): ApiErrorDto {
     }
     return errorObj
   }
-  if ('data' in error.response) {
-    const errorObj: ApiErrorDto = (error.response.data as ApiErrorDto)
-    return errorObj
-  }
-  const errorObj: ApiErrorDto = {
-    status: error.code ? error.code : 'NOT_SET',
-    code: 'error.axios.connection',
-    message: error.message
-  }
+  const errorObj: ApiErrorDto = (error.response.data as ApiErrorDto)
   return errorObj
 }
 
diff --git a/lib/python/dbrepo/api/dto.py b/lib/python/dbrepo/api/dto.py
index c6188098f92b7f33a7b76c08de0ae5beac7355c1..b1caa578cad6fa8c64266dbfe430323d8d5dc4da 100644
--- a/lib/python/dbrepo/api/dto.py
+++ b/lib/python/dbrepo/api/dto.py
@@ -117,7 +117,7 @@ class ColumnBrief(BaseModel):
     database_id: int
     table_id: int
     internal_name: str
-    column_type: ColumnType
+    type: ColumnType
 
 
 class TableBrief(BaseModel):
@@ -889,8 +889,6 @@ class Column(BaseModel):
     ord: int
     internal_name: str
     type: ColumnType
-    is_public: bool
-    is_null_allowed: bool
     alias: Optional[str] = None
     description: Optional[str] = None
     size: Optional[int] = None