diff --git a/core/composer.json b/core/composer.json
index 62c26c4ac5..f08bf72615 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -112,6 +112,7 @@
         "drupal/datetime": "self.version",
         "drupal/datetime_range": "self.version",
         "drupal/dblog": "self.version",
+        "drupal/decoupled_admin": "self.version",
         "drupal/dynamic_page_cache": "self.version",
         "drupal/editor": "self.version",
         "drupal/entity_reference": "self.version",
diff --git a/core/modules/decoupled_admin/decoupled_admin.info.yml b/core/modules/decoupled_admin/decoupled_admin.info.yml
new file mode 100644
index 0000000000..e9e6890654
--- /dev/null
+++ b/core/modules/decoupled_admin/decoupled_admin.info.yml
@@ -0,0 +1,6 @@
+name: 'Decoupled admin'
+type: module
+description: 'Provides endpoints for decoupled admin.'
+package: Experimental
+version: VERSION
+core: 8.x
diff --git a/core/modules/decoupled_admin/src/Plugin/rest/resource/PermissionsCollectionResource.php b/core/modules/decoupled_admin/src/Plugin/rest/resource/PermissionsCollectionResource.php
new file mode 100644
index 0000000000..76a3f38d70
--- /dev/null
+++ b/core/modules/decoupled_admin/src/Plugin/rest/resource/PermissionsCollectionResource.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\decoupled_admin\Plugin\rest\resource;
+
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\rest\ResourceResponse;
+use Drupal\user\PermissionHandlerInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a rest resource for listing available permissions.
+ *
+ * @RestResource(
+ *   id = "permissions_collection",
+ *   label = @Translation("Available permissions"),
+ *   uri_paths = {
+ *     "canonical" = "/permissions"
+ *   }
+ * )
+ */
+class PermissionsCollectionResource extends ResourceBase {
+
+  /**
+   * The permission handler.
+   *
+   * @var \Drupal\user\PermissionHandlerInterface
+   */
+  protected $permissionHandler;
+
+  /**
+   * Constructs a PermissionResource object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param array $serializer_formats
+   *   The available serialization formats.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\user\PermissionHandlerInterface $permission_handler
+   *   The permission handler
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, PermissionHandlerInterface $permission_handler) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
+
+    $this->permissionHandler = $permission_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->getParameter('serializer.formats'),
+      $container->get('logger.factory')->get('rest'),
+      $container->get('user.permissions')
+    );
+  }
+
+  /**
+   * Responds to GET requests.
+   *
+   * @return \Drupal\rest\ResourceResponse
+   *   The response containing the list of available permissions.
+   */
+  public function get() {
+    $permissions = $this->permissionHandler->getPermissions();
+    foreach ($permissions as $id => $permission) {
+      $permissions[$id]['id'] = $id;
+    }
+
+    return new ResourceResponse($permissions);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function permissions() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getBaseRoute($canonical_path, $method) {
+    $route = parent::getBaseRoute($canonical_path, $method);
+    $route->addRequirements(['_permission' => 'administer permissions']);
+    return $route;
+  }
+
+}
diff --git a/core/modules/decoupled_admin/tests/src/Functional/rest/UserPermissionsResourceTest.php b/core/modules/decoupled_admin/tests/src/Functional/rest/UserPermissionsResourceTest.php
new file mode 100644
index 0000000000..b1ccc0edee
--- /dev/null
+++ b/core/modules/decoupled_admin/tests/src/Functional/rest/UserPermissionsResourceTest.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Drupal\Tests\decoupled_admin\Functional\rest;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Url;
+use Drupal\rest\RestResourceConfigInterface;
+use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
+use Drupal\Tests\rest\Functional\ResourceTestBase;
+
+/**
+ * Tests the user permissions resource.
+ *
+ * @group user
+ */
+class UserPermissionsResourceTest extends ResourceTestBase {
+
+  use CookieResourceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $format = 'hal_json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $mimeType = 'application/hal+json';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $auth = 'cookie';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $resourceConfigId = 'permissions_collection';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['hal', 'user', 'rest', 'decoupled_admin'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $auth = isset(static::$auth) ? [static::$auth] : [];
+    $this->provisionResource([static::$format], $auth);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function provisionResource($formats = [], $authentication = []) {
+    $this->resourceConfigStorage->create([
+      'id' => static::$resourceConfigId,
+      'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
+      'configuration' => [
+        'methods' => ['GET'],
+        'formats' => $formats,
+        'authentication' => $authentication,
+      ],
+      'status' => TRUE,
+    ])->save();
+    $this->refreshTestStateAfterRestConfigChange();
+  }
+
+  /**
+   * Writes a log messages and retrieves it via the REST API.
+   */
+  public function testPermissionsCollection() {
+    $this->initAuthentication();
+    $url = Url::fromRoute('rest.permissions_collection.GET', ['_format' => static::$format]);
+    $request_options = $this->getAuthenticationRequestOptions('GET');
+
+    $response = $this->request('GET', $url, $request_options);
+    $this->assertResourceResponse(403, '{"message":"The \u0027administer permissions\u0027 permission is required."}', $response);
+
+    // create a user account that has the required permissions to read
+    // the watchdog resource via the rest api.
+    $this->setUpAuthorization('GET');
+
+    $response = $this->request('GET', $url, $request_options);
+    $this->assertResourceResponse(200, false, $response, ['config:rest.resource.permissions_collection', 'config:rest.settings', 'http_response'], ['user.permissions'], FALSE, 'MISS');
+    $permissions = json::decode((string) $response->getBody());
+
+    $permission_handler = \drupal::service('user.permissions')->getPermissions();
+    $this->assertSame(array_keys($permission_handler), array_keys($permissions));
+    $this->assertSame([
+      'title' => 'Administer permissions',
+      'restrict access' => TRUE,
+      'description' => NULL,
+      'provider' => 'user',
+      'id' => 'administer permissions',
+    ], $permissions['administer permissions']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUpAuthorization($method) {
+    switch ($method) {
+      case 'GET':
+        $this->grantPermissionsToTestedRole(['administer permissions']);
+        break;
+
+      default:
+        throw new \UnexpectedValueException();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedUnauthorizedAccessMessage($method) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getExpectedUnauthorizedAccessCacheability() {}
+
+}
