diff --git a/extra/exports b/extra/exports
index 70cb43665876a3119a886843f0a214d36db395ed..3eaaae0ab435c1bb727fa6fae20c1eaf41e89fda 100644
--- a/extra/exports
+++ b/extra/exports
@@ -12,6 +12,7 @@
 base64_bufsize
 base64_decode
 base64_encode
+box_access_check_space
 box_auth_data_prepare
 box_dd_version_id
 box_decimal_abs
diff --git a/src/box/box.cc b/src/box/box.cc
index b7057e36b64cae77c839715b89af03bb7c8a5dfd..676ed3fa3c3821e07e84065a3ab5e52329cede6b 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -3978,6 +3978,15 @@ box_info_lsn(void)
 	}
 }
 
+API_EXPORT int
+box_access_check_space(uint32_t space_id, uint16_t access)
+{
+	struct space *space = space_cache_find(space_id);
+	if (space == NULL)
+		return -1;
+	return access_check_space(space, access);
+}
+
 static inline void
 box_register_replica(uint32_t id, const struct tt_uuid *uuid)
 {
diff --git a/src/box/box.h b/src/box/box.h
index 73fe3863adfd45d2e259cfb9174651ba8920d2c7..ff158aa8ee88b5fac92ac6982d9dfe815f128a15 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -742,6 +742,21 @@ box_effective_user_id(void);
 API_EXPORT int
 box_user_id_by_name(const char *name, const char *name_end, uint32_t *uid);
 
+/**
+ * Run access check for the current user against
+ * specified space and access type.
+ * While it is possible to pass bitmask in access
+ * parameter this function is intended to be used
+ * with only one permission at a time.
+ * Most relevant access types are read and write.
+ * \param space_id space id
+ * \param access type of access. See valid options in priv_type enum.
+ * \retval -1 on error (check box_error_last())
+ * \retval 0 on success
+ */
+API_EXPORT int
+box_access_check_space(uint32_t space_id, uint16_t access);
+
 /**
  * Sends a packet with the given header and body over the IPROTO session's
  * socket.
diff --git a/src/box/space.h b/src/box/space.h
index 84d2f72c06a93d7df36811b467a09bdf7acfdf2a..5b45d535cb99ec1f85288cf8d86161a67a31190a 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -470,6 +470,10 @@ space_index_def(struct space *space, int n);
 /**
  * Check whether or not the current user can be granted
  * the requested access to the space.
+ * @param space Space to run access check against
+ * @param access Requested access
+ * @retval 0 on success when access is granted
+ * @retval -1 on error (check box_error_last())
  */
 int
 access_check_space(struct space *space, user_access_t access);
diff --git a/test/app-tap/module_api.test.lua b/test/app-tap/module_api.test.lua
index 1788357c5b86e62ecd8b344f1b5c8ef4f2f26016..29e28a765ce9ee2d72dfedce7af6fceef75403c0 100755
--- a/test/app-tap/module_api.test.lua
+++ b/test/app-tap/module_api.test.lua
@@ -663,8 +663,90 @@ local function test_box_iproto_override(test, module)
     module.box_iproto_override_reset(box.iproto.type.PING)
 end
 
+local function test_box_access_check_space(test)
+    test:plan(9)
+
+    local user_name = "box_access_check_space_test_user"
+    box.schema.user.create(user_name, {password = 'foobar'})
+
+    local space_name = "test_box_access_check_space_ffi"
+    box.schema.space.create(space_name)
+
+    ffi = require('ffi')
+    ffi.cdef('int box_access_check_space(uint32_t space_id, uint16_t access);')
+    local function test_access_check(test, access, expected_ret, expected_msg)
+        box.error.clear()
+        local r = ffi.C.box_access_check_space(box.space[space_name].id, access)
+        local e = tostring(box.error.last())
+        test:plan(2)
+        test:is(r, expected_ret, "return value");
+        test:like(e, expected_msg, "error message");
+    end
+
+    box.session.su(user_name, function()
+        test:test("no access - read fails",
+            test_access_check, box.priv.R,
+            -1, "Read access to space .+ is denied for user .+"
+        )
+
+        test:test("no access - write fails",
+            test_access_check, box.priv.W,
+            -1, "Write access to space .+ is denied for user .+"
+        )
+    end)
+
+    box.schema.user.grant(user_name, 'read', 'space', space_name)
+
+    box.session.su(user_name, function()
+        test:test("grant read - read ok",
+            test_access_check, box.priv.R,
+            0, "nil"
+        )
+
+        test:test("grant read - write fails",
+            test_access_check, box.priv.W,
+            -1, "Write access to space .+ is denied for user .+"
+        )
+    end)
+
+    box.schema.user.grant(user_name, 'write', 'space', space_name)
+
+    box.session.su(user_name, function()
+        test:test("grant rw - read ok",
+            test_access_check, box.priv.R,
+            0, "nil"
+        )
+
+        test:test("grant rw - write ok",
+            test_access_check, box.priv.W,
+            0, "nil"
+        )
+
+        -- Execute accesses can't be granted to a space. This case is
+        -- artificial, but access check still fails.
+        test:test("grant rw - execute access check fails",
+            test_access_check, box.priv.X,
+            -1, "Execute access to space .+ is denied for user .+"
+        )
+    end)
+
+    box.schema.user.revoke(user_name, 'read', 'space', space_name)
+
+    box.session.su(user_name, function()
+        test:test("grant write - read fails",
+            test_access_check, box.priv.R,
+            -1, "Read access to space .+ is denied for user .+"
+        )
+
+        test:test("grant write - write ok",
+            test_access_check, box.priv.W,
+            0, "nil"
+        )
+    end)
+end
+
 require('tap').test("module_api", function(test)
-    test:plan(56)
+    test:plan(57)
     local status, module = pcall(require, 'module_api')
     test:is(status, true, "module")
     test:ok(status, "module is loaded")
@@ -701,6 +783,7 @@ require('tap').test("module_api", function(test)
     test:test("box_session_id_matches", test_box_session_id_matches, module)
     test:test("box_iproto_send", test_box_iproto_send, module)
     test:test("box_iproto_override", test_box_iproto_override, module)
+    test:test("box_access_check_space", test_box_access_check_space)
 
     space:drop()
 end)