diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
new file mode 100644
index 0000000..edb9bde
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/Derivative/EntityDerivative.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\Derivative\EntityDerivative.
+ */
+
+namespace Drupal\rest\Plugin\Derivative;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+/**
+ * Provides a resource plugin definition for every entity type.
+ */
+class EntityDerivative implements DerivativeInterface {
+
+  /**
+   * List of derivatives.
+   *
+   * @var array
+   */
+  protected $derivatives;
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    if (!isset($this->derivatives)) {
+      $this->getDerivativeDefinitions($base_plugin_definition);
+    }
+    return $this->derivatives[$derivative_id];
+  }
+
+  /**
+   * Implements DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    if (!isset($this->derivatives)) {
+      // Add in the default plugin configuration and the resource type.
+      foreach (entity_get_info() as $entity_type => $entity_info) {
+        $this->derivatives[$entity_type] = array(
+          'entity_type' => $entity_type,
+          'label' => $entity_info['label'],
+        );
+        $this->derivatives[$entity_type] += $base_plugin_definition;
+      }
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
new file mode 100644
index 0000000..633e225
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/ResourceBase.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\ResourceBase.
+ */
+
+namespace Drupal\rest\Plugin;
+
+use Drupal\Component\Plugin\PluginBase;
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Common base class for resource plugins.
+ */
+abstract class ResourceBase extends PluginBase {
+
+  /**
+   * Temporary helper for building permissions.
+   */
+  public function permissions() {
+    $permissions = array();
+    $definition = $this->getDefinition();
+    foreach ($this->requestMethods() as $method) {
+      $lowered_method = strtolower($method);
+      // Only expose permissions where the HTTP request method exists on the
+      // plugin.
+      if (method_exists($this, $lowered_method)) {
+        $permissions["restful $lowered_method $this->plugin_id"] = array(
+          'title' => t('Access @method on resource %label', array('@method' => $method, '%label' => $definition['label'])),
+        );
+      }
+    }
+    return $permissions;
+  }
+
+  /**
+   * Temporary helper for building routes.
+   *
+   * @return \Symfony\Component\Routing\RouteCollection
+   *   A collection of routes that should be registered for this resource.
+   */
+  public function routes() {
+    $collection = new RouteCollection();
+
+    $methods = $this->requestMethods();
+    foreach ($methods as $method) {
+      // Only expose routes where the HTTP request method exists on the plugin.
+      if (method_exists($this, strtolower($method))) {
+        $prefix = strtr($this->plugin_id, ':', '/');
+        $route = new Route("/$prefix/{id}", array(
+          '_controller' => 'Drupal\rest\RequestHandler::handle',
+          'plugin' => $this->plugin_id,
+        ), array(
+          '_method' => $method,
+          'format' => 'application/ld+json',
+        ));
+
+        $name = strtr($this->plugin_id, ':', '.');
+        $collection->add("$name.$method", $route);
+      }
+    }
+
+    return $collection;
+  }
+
+  /**
+   * Provides predefined HTTP request methods.
+   *
+   * Plugins can override this method to provide additional custom request
+   * methods.
+   *
+   * @return array
+   *   The list of allowed HTTP request method strings.
+   */
+  protected function requestMethods() {
+    return drupal_map_assoc(array(
+      'HEAD',
+      'GET',
+      'POST',
+      'PUT',
+      'DELETE',
+      'TRACE',
+      'OPTIONS',
+      'CONNECT',
+      'PATCH',
+    ));
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/Type/ResourceManager.php b/core/modules/rest/lib/Drupal/rest/Plugin/Type/ResourceManager.php
new file mode 100644
index 0000000..4be45f1
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/Type/ResourceManager.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\Type\ResourceManager.
+ */
+
+namespace Drupal\rest\Plugin\Type;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Component\Plugin\Factory\ReflectionFactory;
+
+/**
+ * Manages discovery and instantiation of resource plugins.
+ */
+class ResourceManager extends PluginManagerBase {
+
+  /**
+   * Overrides Drupal\Component\Plugin\PluginManagerBase::__construct().
+   */
+  public function __construct() {
+    // Create resource plugin derivatives from declaratively defined resources.
+    $this->discovery = new DerivativeDiscoveryDecorator(new AnnotatedClassDiscovery('rest', 'resource'));
+    $this->factory = new ReflectionFactory($this);
+  }
+
+  /**
+   * Overrides Drupal\Component\Plugin\PluginManagerBase::getInstance().
+   */
+  public function getInstance(array $options){
+    if (isset($options['resource_type'])) {
+      // Try plugins with the resource type as ID first.
+      $defintion = $this->getDefinition($options['resource_type']);
+      if ($defintion) {
+        return $this->createInstance($options['resource_type']);
+      }
+      // Otherwise we need to inspect all plugins.
+      $defintions = $this->getDefinitions();
+      foreach ($defintions as $id => $defintion) {
+        if ($defintion['resource_type'] == $options['resource_type']) {
+          return $this->createInstance($id);
+        }
+      }
+    }
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
new file mode 100644
index 0000000..ea4f6f9
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/EntityResource.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\rest\resource\EntityResource.
+ */
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\rest\Plugin\ResourceBase;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Represents entities as resources.
+ *
+ * @Plugin(
+ *  id = "entity",
+ *  label = "Entity",
+ *  derivative = "Drupal\rest\Plugin\Derivative\EntityDerivative"
+ * )
+ */
+class EntityResource extends ResourceBase {
+
+  /**
+   * Responds to entity DELETE requests.
+   *
+   * @param mixed $id
+   *   The entity ID.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The response object.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   */
+  public function delete($id) {
+    $definition = $this->getDefinition();
+    $entity = entity_load($definition['entity_type'], $id);
+    if ($entity) {
+      $entity->delete();
+      // Delete responses have an empty body.
+      return new Response('', 204);
+    }
+    throw new NotFoundHttpException(t('Entity with ID @id not found', array('@id' => $id)));
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/WatchdogResource.php b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/WatchdogResource.php
new file mode 100644
index 0000000..30b7b71
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Plugin/rest/resource/WatchdogResource.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\Plugin\rest\resource\WatchdogResource.
+ */
+
+namespace Drupal\rest\Plugin\rest\resource;
+
+use Drupal\rest\Plugin\ResourceBase;
+use Drupal\Core\Annotation\Plugin;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Provides a resource for database watchdog log entries.
+ *
+ * @Plugin(
+ *  id = "watchdog",
+ *  label = "Watchdog DB log"
+ * )
+ */
+class WatchdogResource extends ResourceBase {
+
+  /**
+   * Overrides \Drupal\rest\Plugin\ResourceBase::routes().
+   */
+  public function routes() {
+    // Only expose routes if the dblog module is enabled.
+    if (module_exists('dblog')) {
+      return parent::routes();
+    }
+    return new RouteCollection();
+  }
+
+  /**
+   * Responds to GET requests.
+   *
+   * Returns a watchdog log entry for the specified ID.
+   *
+   * @return \Symfony\Component\HttpFoundation\Response
+   *   The response object.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
+   */
+  public function get($id = NULL) {
+    if ($id) {
+      $result = db_select('watchdog', 'w')
+        ->condition('wid', $id)
+        ->fields('w')
+        ->execute()
+        ->fetchAll();
+      if (empty($result)) {
+        throw new NotFoundHttpException('Not Found');
+      }
+      // @todo remove hard coded format here.
+      return new Response(drupal_json_encode($result[0]), 200, array('Content-Type' => 'application/json'));
+    }
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/RequestHandler.php b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
new file mode 100644
index 0000000..dfc692a
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/RequestHandler.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\RequestHandler.
+ */
+
+namespace Drupal\rest;
+
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+
+/**
+ * Acts as intermediate request forwarder for resource plugins.
+ */
+class RequestHandler extends ContainerAware {
+
+  /**
+   * Handles a web API request.
+   *
+   * @param string $plugin
+   *   The resource type plugin.
+   * @param Symfony\Component\HttpFoundation\Request $request
+   *   The HTTP request object.
+   * @param mixed $id
+   *   The resource ID.
+   */
+  public function handle($plugin, Request $request, $id = NULL) {
+    $method = strtolower($request->getMethod());
+    if (user_access("restful $method $plugin")) {
+      $resource = $this->container
+        ->get('plugin.manager.rest')
+        ->createInstance($plugin);
+      try {
+        return $resource->{$method}($id);
+      }
+      catch (HttpException $e) {
+        return new Response($e->getMessage(), $e->getStatusCode(), $e->getHeaders());
+      }
+    }
+    return new Response('Access Denied', 403);
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/RestBundle.php b/core/modules/rest/lib/Drupal/rest/RestBundle.php
new file mode 100644
index 0000000..37999d5
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/RestBundle.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\RestBundle.
+ */
+
+namespace Drupal\rest;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Rest dependency injection container.
+ */
+class RestBundle extends Bundle {
+
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    // Register the RestManager class with the dependency injection container.
+    $container->register('plugin.manager.rest', 'Drupal\rest\Plugin\Type\ResourceManager');
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
new file mode 100644
index 0000000..ac324d2
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/DeleteTest.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\DeleteTest.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\rest\Tests\RESTTestBase;
+
+/**
+ * Tests resource deletion on tst entities.
+ */
+class DeleteTest extends RESTTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('rest', 'entity_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Delete resource',
+      'description' => 'Tests the deletion of resources.',
+      'group' => 'REST',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    // Enable web API for test entity and nodes.
+    $config = config('rest');
+    $config->set('resources', array(
+      'entity:node' => 'entity:node',
+      'entity:entity_test' => 'entity:entity_test',
+    ));
+    $config->save();
+
+    // Rebuild routing cache, so that the web API paths are available.
+    drupal_container()->get('router.builder')->rebuild();
+    // Reset the Simpletest permission cache, so that the new resource
+    // permissions get picked up.
+    drupal_static_reset('checkPermissions');
+  }
+
+  /**
+   * Tests several valid and invalid delete requests on entity_test and node.
+   */
+  public function testDelete() {
+    // Create a user account that has the required permissions to delete
+    // resources via the web API.
+    $account = $this->drupalCreateUser(array('restful delete entity:entity_test', 'restful delete entity:node'));
+    $this->drupalLogin($account);
+
+    // Create a test entity programmatically.
+    $entity = entity_create('entity_test', array('name' => 'test', 'user_id' => 1));
+    $entity->save();
+    // Delete it over the web API.
+    $response = $this->httpRequest('entity/entity_test/' . $entity->id(), 'DELETE');
+    // Clear the static cache, otherwise we won't see the update.
+    $entity = entity_load('entity_test', $entity->id(), TRUE);
+    $this->assertFalse($entity, 'Test entity is not in the DB anymore.');
+    $this->assertResponse('204', 'HTTP response code is correct.');
+    $this->assertEqual($response, '', 'Response body is empty.');
+
+    // Create a node programmatically.
+    $entity = entity_create('node', array('title' => 'foo'));
+    $entity->save();
+    // Delete it over the web API.
+    $response = $this->httpRequest('entity/node/' . $entity->id(), 'DELETE');
+    // Clear the static cache, otherwise we won't see the update.
+    $entity = entity_load('node', $entity->id(), TRUE);
+    $this->assertFalse($entity, 'Node is not in the DB anymore.');
+    $this->assertResponse('204', 'HTTP response code is correct.');
+    $this->assertEqual($response, '', 'Response body is empty.');
+
+    // Try to delete a node that does not exist.
+    $response = $this->httpRequest('entity/node/9999', 'DELETE');
+    $this->assertResponse(404);
+    $this->assertEqual($response, 'Entity with ID 9999 not found', 'Response message is correct.');
+
+    // Try to delete a resource which is not web API enabled.
+    $this->httpRequest('entity/user/' . $account->id(), 'DELETE');
+    $user = entity_load('user', $account->id(), TRUE);
+    $this->assertEqual($account->id(), $user->id());
+    $this->assertResponse(404);
+
+    // Try to delete a node without proper permissions.
+    $this->drupalLogout();
+    // Re-save node to the database.
+    $entity = entity_create('node', array('title' => 'foo'));
+    $entity->save();
+    $this->httpRequest('entity/node/' . $entity->id(), 'DELETE');
+    $this->assertResponse(403);
+    $this->assertNotIdentical(FALSE, entity_load('node', $entity->id(), TRUE), 'The entity is still in the database.');
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
new file mode 100644
index 0000000..3bc8756
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/RESTTestBase.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\RESTTestBase.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Test helper class that provides a REST client method to send HTTP requests.
+ */
+abstract class RESTTestBase extends WebTestBase {
+
+  /**
+   * Helper function to issue a HTTP request with simpletest's cURL.
+   *
+   * @param string $url
+   *   The relative URL, e.g. "entity/node/1"
+   * @param string $method
+   *   HTTP method, one of GET, POST, PUT or DELETE.
+   * @param array $body
+   *   Either the body for POST and PUT or additional URL parameters for GET.
+   * @param string $format
+   *   The MIME type of the transmitted content.
+   */
+  protected function httpRequest($url, $method, $body = NULL, $format = 'application/ld+json') {
+    switch ($method) {
+      case 'GET':
+        // Set query if there are additional GET parameters.
+        $options = isset($body) ? array('absolute' => TRUE, 'query' => $body) : array('absolute' => TRUE);
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => TRUE,
+          CURLOPT_URL => url($url, $options),
+          CURLOPT_NOBODY => FALSE)
+        );
+      case 'POST':
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => FALSE,
+          CURLOPT_POST => TRUE,
+          CURLOPT_POSTFIELDS => $body,
+          CURLOPT_URL => url($url, array('absolute' => TRUE)),
+          CURLOPT_NOBODY => FALSE,
+          CURLOPT_HTTPHEADER => array('Content-Type: ' . $format),
+        ));
+      case 'PUT':
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => FALSE,
+          CURLOPT_CUSTOMREQUEST => 'PUT',
+          CURLOPT_POSTFIELDS => $body,
+          CURLOPT_URL => url($url, array('absolute' => TRUE)),
+          CURLOPT_NOBODY => FALSE,
+          CURLOPT_HTTPHEADER => array('Content-Type: ' . $format),
+        ));
+      case 'DELETE':
+        return $this->curlExec(array(
+          CURLOPT_HTTPGET => FALSE,
+          CURLOPT_CUSTOMREQUEST => 'DELETE',
+          CURLOPT_URL => url($url, array('absolute' => TRUE)),
+          CURLOPT_NOBODY => FALSE,
+        ));
+    }
+  }
+}
diff --git a/core/modules/rest/lib/Drupal/rest/Tests/WatchdogTest.php b/core/modules/rest/lib/Drupal/rest/Tests/WatchdogTest.php
new file mode 100644
index 0000000..75d0ac0
--- /dev/null
+++ b/core/modules/rest/lib/Drupal/rest/Tests/WatchdogTest.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\rest\test\WatchdogTest.
+ */
+
+namespace Drupal\rest\Tests;
+
+use Drupal\rest\Tests\RESTTestBase;
+
+/**
+ * Tests the Watchdog resource to retrieve log messages.
+ */
+class WatchdogTest extends RESTTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('rest', 'dblog');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Watchdog resource',
+      'description' => 'Tests the watchdog resource.',
+      'group' => 'REST',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp();
+    // Enable web API for the watchdog resource.
+    $config = config('rest');
+    $config->set('resources', array(
+      'watchdog' => 'watchdog',
+    ));
+    $config->save();
+
+    // Rebuild routing cache, so that the web API paths are available.
+    drupal_container()->get('router.builder')->rebuild();
+    // Reset the Simpletest permission cache, so that the new resource
+    // permissions get picked up.
+    drupal_static_reset('checkPermissions');
+  }
+
+  /**
+   * Writes a log messages and retrieves it via the web API.
+   */
+  public function testWatchdog() {
+    // Write a log message to the DB.
+    watchdog('rest_test', 'Test message');
+    // Get ID of the written message.
+    $result = db_select('watchdog', 'w')
+      ->condition('type', 'rest_test')
+      ->fields('w', array('wid'))
+      ->execute()
+      ->fetchCol();
+    $id = $result[0];
+
+    // Create a user account that has the required permissions to read
+    // the watchdog resource via the web API.
+    $account = $this->drupalCreateUser(array('restful get watchdog'));
+    $this->drupalLogin($account);
+
+    $response = $this->httpRequest("watchdog/$id", 'GET');
+    $this->assertResponse(200);
+    $log = drupal_json_decode($response);
+    $this->assertEqual($log['wid'], $id, 'Log ID is correct.');
+    $this->assertEqual($log['type'], 'rest_test', 'Type of log message is correct.');
+    $this->assertEqual($log['message'], 'Test message', 'Log message text is correct.');
+
+    // Request an unknown log entry.
+    $response = $this->httpRequest("watchdog/9999", 'GET');
+    $this->assertResponse(404);
+    $this->assertEqual($response, 'Not Found', 'Response message is correct.');
+  }
+}
diff --git a/core/modules/rest/rest.admin.inc b/core/modules/rest/rest.admin.inc
new file mode 100644
index 0000000..f151efa
--- /dev/null
+++ b/core/modules/rest/rest.admin.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Admin pages for REST module.
+ */
+
+/**
+ * Form constructor for the REST admin form.
+ *
+ * @ingroup forms
+ */
+function rest_admin_form($form, &$form_state) {
+  $resources = drupal_container()
+    ->get('plugin.manager.rest')
+    ->getDefinitions();
+  $checkboxes = array_map(function ($e) { return $e['label']; }, $resources);
+  asort($checkboxes);
+
+  $form['resources'] = array(
+    '#type' => 'checkboxes',
+    '#options' => $checkboxes,
+    '#default_value' => config('rest')->get('resources') ?: array(),
+    '#title' => t('Select the resource types that should be exposed as web services.'),
+  );
+
+  return system_config_form($form, $form_state);
+}
+
+/**
+ * Form submission handler for rest_admin_form().
+ */
+function rest_admin_form_submit($form, &$form_state) {
+  $config = config('rest');
+  $config->set('resources', array_filter($form_state['values']['resources']));
+  $config->save();
+
+  // Rebuild routing cache.
+  drupal_container()->get('router.builder')->rebuild();
+}
diff --git a/core/modules/rest/rest.info b/core/modules/rest/rest.info
new file mode 100644
index 0000000..56c650c
--- /dev/null
+++ b/core/modules/rest/rest.info
@@ -0,0 +1,6 @@
+name = RESTful web services
+description = Exposes entities and other resources as RESTful web API
+package = Core
+version = VERSION
+core = 8.x
+configure = admin/config/services/rest
diff --git a/core/modules/rest/rest.install b/core/modules/rest/rest.install
new file mode 100644
index 0000000..a53920a
--- /dev/null
+++ b/core/modules/rest/rest.install
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Install, update, and uninstall functions for the REST module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ *
+ * @todo is this needed when using the config system?
+ */
+function rest_uninstall() {
+  config('rest')->delete();
+}
diff --git a/core/modules/rest/rest.module b/core/modules/rest/rest.module
new file mode 100644
index 0000000..5f396ac
--- /dev/null
+++ b/core/modules/rest/rest.module
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * RESTful web services module.
+ */
+
+use Symfony\Component\Routing\Route;
+use Symfony\Component\Routing\RouteCollection;
+
+/**
+ * Implements hook_menu().
+ *
+ * @todo for now we use this hook for our admin page until
+ * http://drupal.org/node/1801570 is sorted out.
+ */
+function rest_menu() {
+  $items['admin/config/services/rest'] = array(
+    'title' => 'RESTful web services',
+    'description' => 'Configure resources and entities that are exposed as web API.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('rest_admin_form'),
+    'access arguments' => array('administer site configuration'),
+    'file' => 'rest.admin.inc',
+  );
+  return $items;
+}
+
+/**
+ * Implements hook_route_info().
+ */
+function rest_route_info() {
+  $collection = new RouteCollection();
+
+  // This hook is called during module enabling where the manager has not been
+  // registered as service yet.
+  if (drupal_container()->has('plugin.manager.rest')) {
+    $manager = drupal_container()->get('plugin.manager.rest');
+    $resources = config('rest')->get('resources');
+    if ($resources && $enabled = array_intersect_key($manager->getDefinitions(), $resources)) {
+      foreach ($enabled as $key => $resource) {
+        $plugin = $manager->createInstance($key);
+
+        // @todo Switch to ->addCollection() once http://drupal.org/node/1819018 is resolved.
+        foreach ($plugin->routes() as $name => $route) {
+          $collection->add("rest.$name", $route);
+        }
+      }
+    }
+  }
+
+  return $collection;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function rest_permission() {
+  $permissions = array();
+  if (drupal_container()->has('plugin.manager.rest')) {
+    $manager = drupal_container()->get('plugin.manager.rest');
+    $resources = config('rest')->get('resources');
+    if ($resources && $enabled = array_intersect_key($manager->getDefinitions(), $resources)) {
+      foreach ($enabled as $key => $resource) {
+        $plugin = $manager->createInstance($key);
+        $permissions = array_merge($permissions, $plugin->permissions());
+      }
+    }
+  }
+  return $permissions;
+}
