diff --git a/core/modules/hal/lib/Drupal/hal/Encoder/JsonEncoder.php b/core/modules/hal/lib/Drupal/hal/Encoder/JsonEncoder.php
index 6d1da5d..4d932ff 100644
--- a/core/modules/hal/lib/Drupal/hal/Encoder/JsonEncoder.php
+++ b/core/modules/hal/lib/Drupal/hal/Encoder/JsonEncoder.php
@@ -30,4 +30,11 @@ public function supportsEncoding($format) {
     return $format == $this->format;
   }
 
+  /**
+   * Overrides \Symfony\Component\Serializer\Encoder\JsonEncoder::supportsDecoding()
+   */
+  public function supportsDecoding($format) {
+    return $format == $this->format;
+  }
+
 }
diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
index 0613d2a..95b5858 100644
--- a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityNormalizer.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityNG;
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 
 /**
  * Converts the Drupal entity object structure to a HAL array structure.
@@ -62,6 +63,58 @@ public function normalize($entity, $format = NULL, array $context = array()) {
   }
 
   /**
+   * Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize().
+   *
+   * @throws \Symfony\Component\Serializer\Exception\UnexpectedValueException
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = array()) {
+    // Get type, necessary for determining which bundle to create.
+    if (!isset($data['_links']['type'])) {
+      throw new UnexpectedValueException('The type link relation must be specified.');
+    }
+
+    // Get language. language_default() is used because otherwise the entity
+    // system cannot understand the langcode when working in the DrupalUnitTest
+    // environment and importing data without an explicit langcode.
+    $langcode = isset($data['langcode']) ? $data['langcode'][0]['value'] : language_default();
+
+    // Create the entity.
+    $typed_data_ids = $this->getTypedDataIds($data['_links']['type']);
+    $entity = entity_create($typed_data_ids['entity_type'], array('langcode' => $langcode, 'type' => $typed_data_ids['bundle']));
+
+    // Get links and remove from data array.
+    $links = $data['_links'];
+    unset($data['_links']);
+    // Get embedded resources and remove from data array.
+    $embedded = array();
+    if (isset($data['_embedded'])) {
+      $embedded = $data['_embedded'];
+      unset($data['_embedded']);
+    }
+
+    // Iterate through remaining items in data array. These should all
+    // correspond to fields.
+    foreach ($data as $field_name => $field_data) {
+      // Remove any values that were set as a part of entity creation (e.g
+      // uuid). If this field is set to an empty array in the data, this will
+      // also have the effect of marking the field for deletion in REST module.
+      $entity->{$field_name} = array();
+
+      $field = $entity->get($field_name);
+      // Get the class of the field. This will generally be the default Field
+      // class.
+      $class = get_class($field);
+      // Pass in the empty field object as a target instance. Since the context
+      // is already prepared for the field, any data added to it is
+      // automatically added to the entity.
+      $context['target_instance'] = $field;
+      $this->serializer->denormalize($field_data, $class, $format, $context);
+    }
+
+    return $entity;
+  }
+
+  /**
    * Constructs the entity URI.
    *
    * @param $entity
@@ -75,4 +128,42 @@ protected function getEntityUri($entity) {
     return url($uri_info['path'], array('absolute' => TRUE));
   }
 
+  /**
+   * Gets the typed data IDs for a type URI.
+   *
+   * @param array $types
+   *   The type array(s) (value of the 'type' attribute of the incoming data).
+   *
+   * @return array
+   *   The typed data IDs.
+   */
+  protected function getTypedDataIds($types) {
+    // The 'type' can potentially contain an array of type objects. By default,
+    // Drupal only uses a single type in serializing, but allows for multiple
+    // types when deserializing.
+    if (isset($types['href'])) {
+      $types = array($types);
+    }
+
+    foreach ($types as $type) {
+      if (!isset($type['href'])) {
+        throw new UnexpectedValueException('Type must contain an \'href\' attribute.');
+      }
+      $type_uri = $type['href'];
+      // Check whether the URI corresponds to a known type on this site. Break
+      // once one does.
+      if ($typed_data_ids = $this->linkManager->getTypeInternalIds($type['href'])) {
+        break;
+      }
+    }
+
+    // If none of the URIs correspond to an entity type on this site, no entity
+    // can be created. Throw an exception.
+    if (empty($typed_data_ids)) {
+      throw new UnexpectedValueException(sprintf('Type %s does not correspond to an entity on this site.', $type_uri));
+    }
+
+    return $typed_data_ids;
+  }
+
 }
diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php
index b56c2bb..76c43c7 100644
--- a/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php
+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/EntityReferenceItemNormalizer.php
@@ -57,4 +57,11 @@ public function normalize($field_item, $format = NULL, array $context = array())
     );
   }
 
+  /**
+   * Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = array()) {
+    // @todo Implement this in http://drupal.org/node/1880424
+  }
+
 }
diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php
index b06a90f..2d3e967 100644
--- a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php
+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldItemNormalizer.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\hal\Normalizer;
 
+use Drupal\Core\Entity\Field\FieldItemInterface;
+
 /**
  * Converts the Drupal field item object structure to HAL array structure.
  */
@@ -38,4 +40,72 @@ public function normalize($field_item, $format = NULL, array $context = array())
     );
   }
 
+  /**
+   * Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = array()) {
+    if (!isset($context['target_instance'])) {
+      throw new LogicException('$context[\'target_instance\'] must be set to denormalize with the FieldItemNormalizer');
+    }
+    if ($context['target_instance']->getParent() == NULL) {
+      throw new LogicException('The field item passed in via $context[\'target_instance\'] must have a parent set.');
+    }
+
+    $field_item = $context['target_instance'];
+
+    // If this field is translatable, we need to create a translated instance.
+    if (isset($data['lang'])) {
+      $langcode = $data['lang'];
+      unset($data['lang']);
+      $field_definition = $field_item->getDefinition();
+      if ($field_definition['translatable'] == TRUE) {
+        $field_item = $this->createTranslatedInstance($field_item, $langcode);
+      }
+    }
+
+    $field_item->setValue($data);
+    return $field_item;
+  }
+
+  /**
+   * Get a translated version of the field item instance.
+   *
+   * To indicate that a field item applies to one translation of an entity and
+   * not another, the property path must originate with a translation of the
+   * entity. This is the reason for using target_instances, from which the
+   * property path can be traversed up to the root.
+   *
+   * @param \Drupal\Core\Entity\Field\FieldItemInterface $field_item
+   *   The untranslated field item instance.
+   * @param $langcode
+   *   The langcode.
+   *
+   * @return \Drupal\Core\Entity\Field\FieldItemInterface
+   *   The translated field item instance.
+   */
+  protected function createTranslatedInstance(FieldItemInterface $field_item, $langcode) {
+    $parent = $field_item->getParent();
+    $ancestors = array();
+
+    // Remove the untranslated instance from the field's list of items.
+    $parent->offsetUnset($field_item->getName());
+
+    // Get the property path.
+    while (!method_exists($parent, 'getTranslation')) {
+      array_unshift($ancestors, $parent);
+      $parent = $parent->getParent();
+    }
+
+    // Recreate the property path with translations.
+    $translation = $parent->getTranslation($langcode);
+    foreach ($ancestors as $ancestor) {
+      $ancestor_name =  $ancestor->getName();
+      $translation = $translation->get($ancestor_name);
+    }
+
+    // Create a new instance at the end of the property path and return it.
+    $count = $translation->isEmpty() ? 0 : $translation->count();
+    return $translation->offsetGet($count);
+  }
+
 }
diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php
index d088bca..d625234 100644
--- a/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php
+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/FieldNormalizer.php
@@ -8,6 +8,7 @@
 namespace Drupal\hal\Normalizer;
 
 use Drupal\Component\Utility\NestedArray;
+use Symfony\Component\Serializer\Exception\LogicException;
 
 /**
  * Converts the Drupal field structure to HAL array structure.
@@ -26,6 +27,8 @@ class FieldNormalizer extends NormalizerBase {
    */
   public function normalize($field, $format = NULL, array $context = array()) {
     $normalized_field_items = array();
+
+    // Get the field definition.
     $entity = $field->getParent();
     $field_name = $field->getName();
     $field_definition = $entity->getPropertyDefinition($field_name);
@@ -53,6 +56,34 @@ public function normalize($field, $format = NULL, array $context = array()) {
     return $normalized;
   }
 
+
+  /**
+   * Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::denormalize()
+   */
+  public function denormalize($data, $class, $format = NULL, array $context = array()) {
+    if (!isset($context['target_instance'])) {
+      throw new LogicException('$context[\'target_instance\'] must be set to denormalize with the FieldNormalizer');
+    }
+    if ($context['target_instance']->getParent() == NULL) {
+      throw new LogicException('The field passed in via $context[\'target_instance\'] must have a parent set.');
+    }
+
+    $field = $context['target_instance'];
+    foreach ($data as $field_item_data) {
+      $count = $field->count();
+      // Get the next field item instance. The offset will serve as the field
+      // item name.
+      $field_item = $field->offsetGet($count);
+      $field_item_class = get_class($field_item);
+      // Pass in the empty field item object as the target instance.
+      $context['target_instance'] = $field_item;
+      $this->serializer->denormalize($field_item_data, $field_item_class, $format, $context);
+    }
+
+    return $field;
+
+  }
+
   /**
    * Helper function to normalize field items.
    *
diff --git a/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php b/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php
index d159f1f..afa9019 100644
--- a/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php
+++ b/core/modules/hal/lib/Drupal/hal/Normalizer/NormalizerBase.php
@@ -8,11 +8,12 @@
 namespace Drupal\hal\Normalizer;
 
 use Drupal\serialization\Normalizer\NormalizerBase as SerializationNormalizerBase;
+use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 
 /**
  * Base class for Normalizers.
  */
-abstract class NormalizerBase extends SerializationNormalizerBase {
+abstract class NormalizerBase extends SerializationNormalizerBase implements DenormalizerInterface {
 
   /**
    * The formats that the Normalizer can handle.
@@ -36,6 +37,22 @@ public function supportsNormalization($data, $format = NULL) {
   }
 
   /**
+   * Implements \Symfony\Component\Serializer\Normalizer\DenormalizerInterface::supportsDenormalization()
+   */
+  public function supportsDenormalization($data, $type, $format = NULL) {
+    if (in_array($format, $this->formats)) {
+      $target = new \ReflectionClass($type);
+      $supported = new \ReflectionClass($this->supportedInterfaceOrClass);
+      if ($supported->isInterface()) {
+        return $target->implementsInterface($this->supportedInterfaceOrClass);
+      }
+      else {
+        return ($target->getName() == $this->supportedInterfaceOrClass || $target->isSubclassOf($this->supportedInterfaceOrClass));
+      }
+    }
+  }
+
+  /**
    * Sets the link manager.
    *
    * The link manager determines the hypermedia type and relation links which
diff --git a/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
new file mode 100644
index 0000000..c4164f8
--- /dev/null
+++ b/core/modules/hal/lib/Drupal/hal/Tests/DenormalizeTest.php
@@ -0,0 +1,185 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\hal\Tests\DenormalizeTest.
+ */
+
+namespace Drupal\hal\Tests;
+
+use Symfony\Component\Serializer\Exception\UnexpectedValueException;
+
+/**
+ * Test the HAL normalizer's denormalize function.
+ */
+class DenormalizeTest extends NormalizerTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Denormalize Test',
+      'description' => 'Test that entities can be denormalized from HAL.',
+      'group' => 'HAL',
+    );
+  }
+
+  /**
+   * Tests that the type link relation in incoming data is handled correctly.
+   */
+  public function testTypeHandling() {
+    // Valid type.
+    $data_with_valid_type = array(
+      '_links' => array(
+        'type' => array(
+          'href' => url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+        ),
+      ),
+    );
+    $denormalized = $this->container->get('serializer')->denormalize($data_with_valid_type, $this->entityClass, $this->format);
+    $this->assertEqual(get_class($denormalized), $this->entityClass, 'Request with valid type results in creation of correct bundle.');
+
+    // Multiple types.
+    $data_with_multiple_types = array(
+      '_links' => array(
+        'type' => array(
+          array(
+            'href' => url('rest/types/foo', array('absolute' => TRUE)),
+          ),
+          array(
+            'href' => url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+          ),
+        ),
+      ),
+    );
+    $denormalized = $this->container->get('serializer')->denormalize($data_with_multiple_types, $this->entityClass, $this->format);
+    $this->assertEqual(get_class($denormalized), $this->entityClass, 'Request with multiple types results in creation of correct bundle.');
+
+    // Invalid type.
+    $data_with_invalid_type = array(
+      '_links' => array(
+        'type' => array(
+          'href' => url('rest/types/foo', array('absolute' => TRUE)),
+        ),
+      ),
+    );
+    try {
+      $this->container->get('serializer')->denormalize($data_with_invalid_type, $this->entityClass, $this->format);
+      $this->fail('Exception thrown when type is invalid.');
+    }
+    catch (UnexpectedValueException $e) {
+      $this->pass('Exception thrown when type is invalid.');
+    }
+
+    // No type.
+    $data_with_no_type = array(
+      '_links' => array(
+      ),
+    );
+    try {
+      $this->container->get('serializer')->denormalize($data_with_no_type, $this->entityClass, $this->format);
+      $this->fail('Exception thrown when no type is provided.');
+    }
+    catch (UnexpectedValueException $e) {
+      $this->pass('Exception thrown when no type is provided.');
+    }
+  }
+
+  /**
+   * Test that a field set to an empty array is different than an empty field.
+   */
+  public function testMarkFieldForDeletion() {
+    $no_field_data = array(
+      '_links' => array(
+        'type' => array(
+          'href' => url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+        ),
+      ),
+    );
+    $no_field_denormalized = $this->container->get('serializer')->denormalize($no_field_data, $this->entityClass, $this->format);
+    $no_field_value = $no_field_denormalized->field_test_text->getValue();
+
+    $empty_field_data = array(
+      '_links' => array(
+        'type' => array(
+          'href' => url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+        ),
+      ),
+      'field_test_text' => array(),
+    );
+    $empty_field_denormalized = $this->container->get('serializer')->denormalize($empty_field_data, $this->entityClass, $this->format);
+    $empty_field_value = $empty_field_denormalized->field_test_text->getValue();
+
+    $this->assertTrue(!empty($no_field_value) && empty($empty_field_value), 'A field set to an empty array in the data is structured differently than an empty field.');
+  }
+
+  /**
+   * Test that non-reference fields can be denormalized.
+   */
+  public function testBasicFieldDenormalization() {
+    $data = array(
+      '_links' => array(
+        'type' => array(
+          'href' => url('rest/type/entity_test/entity_test', array('absolute' => TRUE)),
+        ),
+      ),
+      'uuid' => array(
+        array(
+          'value' => 'e5c9fb96-3acf-4a8d-9417-23de1b6c3311',
+        ),
+      ),
+      'field_test_text' => array(
+        array(
+          'value' => $this->randomName(),
+          'format' => 'full_html',
+        ),
+      ),
+      'field_test_translatable_text' => array(
+        array(
+          'value' => $this->randomName(),
+          'format' => 'full_html',
+          'lang' => 'en',
+        ),
+        array(
+          'value' => $this->randomName(),
+          'format' => 'filtered_html',
+          'lang' => 'en',
+        ),
+        array(
+          'value' => $this->randomName(),
+          'format' => 'filtered_html',
+          'lang' => 'de',
+        ),
+        array(
+          'value' => $this->randomName(),
+          'format' => 'full_html',
+          'lang' => 'de',
+        ),
+      ),
+    );
+
+    $expected_value_en = array(
+      array (
+        'value' => $data['field_test_translatable_text'][0]['value'],
+        'format' => 'full_html',
+      ),
+      array (
+        'value' => $data['field_test_translatable_text'][1]['value'],
+        'format' => 'filtered_html',
+      ),
+    );
+    $expected_value_de = array(
+      array (
+        'value' => $data['field_test_translatable_text'][2]['value'],
+        'format' => 'filtered_html',
+      ),
+      array (
+        'value' => $data['field_test_translatable_text'][3]['value'],
+        'format' => 'full_html',
+      ),
+    );
+    $denormalized = $this->container->get('serializer')->denormalize($data, $this->entityClass, $this->format);
+    $this->assertEqual($data['uuid'], $denormalized->get('uuid')->getValue(), 'A preset value (e.g. UUID) is overridden by incoming data.');
+    $this->assertEqual($data['field_test_text'], $denormalized->get('field_test_text')->getValue(), 'A basic text field is denormalized.');
+    $this->assertEqual($expected_value_en, $denormalized->get('field_test_translatable_text')->getValue(), 'Values in the default language are properly handled for a translatable field.');
+    $this->assertEqual($expected_value_de, $denormalized->getTranslation('de')->get('field_test_translatable_text')->getValue(), 'Values in a translation language are properly handled for a translatable field.');
+  }
+}
diff --git a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php
index 224586d..9cf7019 100644
--- a/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php
+++ b/core/modules/hal/lib/Drupal/hal/Tests/NormalizerTestBase.php
@@ -30,6 +30,13 @@
   protected $format = 'hal_json';
 
   /**
+   * The class name of the test class.
+   *
+   * @var string
+   */
+  protected $entityClass = 'Drupal\entity_test\Plugin\Core\Entity\EntityTest';
+
+  /**
    * Overrides \Drupal\simpletest\DrupalUnitTestBase::setup().
    */
   function setUp() {
@@ -57,7 +64,6 @@ function setUp() {
     $field = array(
       'field_name' => 'field_test_text',
       'type' => 'text',
-      'cardinality' => 1,
       'translatable' => FALSE,
     );
     field_create_field($field);
@@ -68,6 +74,20 @@ function setUp() {
     );
     field_create_instance($instance);
 
+    // Create the test translatable field.
+    $field = array(
+      'field_name' => 'field_test_translatable_text',
+      'type' => 'text',
+      'translatable' => TRUE,
+    );
+    field_create_field($field);
+    $instance = array(
+      'entity_type' => 'entity_test',
+      'field_name' => 'field_test_translatable_text',
+      'bundle' => 'entity_test',
+    );
+    field_create_instance($instance);
+
     // Create the test entity reference field.
     $field = array(
       'translatable' => TRUE,
diff --git a/core/modules/rest/lib/Drupal/rest/LinkManager/LinkManager.php b/core/modules/rest/lib/Drupal/rest/LinkManager/LinkManager.php
index 3f5b947..2004a6c 100644
--- a/core/modules/rest/lib/Drupal/rest/LinkManager/LinkManager.php
+++ b/core/modules/rest/lib/Drupal/rest/LinkManager/LinkManager.php
@@ -43,6 +43,13 @@ public function getTypeUri($entity_type, $bundle) {
   }
 
   /**
+   * Implements \Drupal\rest\LinkManager\TypeLinkManagerInterface::getTypeInternalIds().
+   */
+  public function getTypeInternalIds($type_uri) {
+    return $this->typeLinkManager->getTypeInternalIds($type_uri);
+  }
+
+  /**
    * Implements \Drupal\rest\LinkManager\RelationLinkManagerInterface::getRelationUri().
    */
   public function getRelationUri($entity_type, $bundle, $field_name) {
diff --git a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
index 7988bfe..6802282 100644
--- a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
+++ b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManager.php
@@ -7,9 +7,28 @@
 
 namespace Drupal\rest\LinkManager;
 
+use Drupal\Core\Cache\CacheBackendInterface;
+
 class TypeLinkManager implements TypeLinkManagerInterface {
 
   /**
+   * Injected cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface;
+   */
+  protected $cache;
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The injected cache backend for caching type URIs.
+   */
+  public function __construct(CacheBackendInterface $cache) {
+    $this->cache = $cache;
+  }
+
+  /**
    * Get a type link for a bundle.
    *
    * @param string $entity_type
@@ -25,4 +44,62 @@ public function getTypeUri($entity_type, $bundle) {
     return url("rest/type/$entity_type/$bundle", array('absolute' => TRUE));
   }
 
+  /**
+   * Implements \Drupal\rest\LinkManager\TypeLinkManagerInterface::getTypeInternalIds().
+   */
+  public function getTypeInternalIds($type_uri) {
+    $types = $this->getTypes();
+    if (isset($types[$type_uri])) {
+      return $types[$type_uri];
+    }
+    return FALSE;
+  }
+
+  /**
+   * Get the array of type links.
+   *
+   * @return array
+   *   An array of typed data ids (entity_type and bundle) keyed by
+   *   corresponding type URI.
+   */
+  public function getTypes() {
+    $cid = 'rest:links:types';
+    $cache = $this->cache->get($cid);
+    if (!$cache) {
+      $this->writeCache();
+      $cache = $this->cache->get($cid);
+    }
+    return $cache->data;
+  }
+
+  /**
+   * Writes the cache of type links.
+   */
+  protected function writeCache() {
+    $data = array();
+
+    // Type URIs correspond to bundles. Iterate through the bundles to get the
+    // URI and data for them.
+    $entity_info = entity_get_info();
+    foreach (entity_get_bundles() as $entity_type => $bundles) {
+      $entity_type_info = $entity_info[$entity_type];
+      $reflection = new \ReflectionClass($entity_type_info['class']);
+      // Only content entities are supported currently.
+      // @todo Consider supporting config entities.
+      if ($reflection->implementsInterface('\Drupal\Core\Config\Entity\ConfigEntityInterface')) {
+        continue;
+      }
+      foreach ($bundles as $bundle => $bundle_info) {
+        // Get a type URI for the bundle.
+        $bundle_uri = $this->getTypeUri($entity_type, $bundle);
+        $data[$bundle_uri] = array(
+          'entity_type' => $entity_type,
+          'bundle' => $bundle,
+        );
+      }
+    }
+    // These URIs only change when entity info changes, so cache it permanently
+    // and only clear it when entity_info is cleared.
+    $this->cache->set('rest:links:types', $data, CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
+  }
 }
diff --git a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManagerInterface.php b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManagerInterface.php
index ba4dc3c..9c2d942 100644
--- a/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManagerInterface.php
+++ b/core/modules/rest/lib/Drupal/rest/LinkManager/TypeLinkManagerInterface.php
@@ -25,4 +25,16 @@
    *   The corresponding URI for the bundle.
    */
   public function getTypeUri($entity_type, $bundle);
+
+  /**
+   * Get a bundle's Typed Data IDs based on a URI.
+   *
+   * @param string $type_uri
+   *   The type URI.
+   *
+   * @return array | boolean
+   *   If the URI matches a bundle, returns an array containing entity_type and
+   *   bundle. Otherwise, returns false.
+   */
+  public function getTypeInternalIds($type_uri);
 }
diff --git a/core/modules/rest/lib/Drupal/rest/RestBundle.php b/core/modules/rest/lib/Drupal/rest/RestBundle.php
index 37db78e..1c98b79 100644
--- a/core/modules/rest/lib/Drupal/rest/RestBundle.php
+++ b/core/modules/rest/lib/Drupal/rest/RestBundle.php
@@ -36,7 +36,8 @@ public function build(ContainerBuilder $container) {
     $container->register('rest.link_manager', 'Drupal\rest\LinkManager\LinkManager')
       ->addArgument(new Reference('rest.link_manager.type'))
       ->addArgument(new Reference('rest.link_manager.relation'));
-    $container->register('rest.link_manager.type', 'Drupal\rest\LinkManager\TypeLinkManager');
+    $container->register('rest.link_manager.type', 'Drupal\rest\LinkManager\TypeLinkManager')
+      ->addArgument(new Reference('cache.cache'));
     $container->register('rest.link_manager.relation', 'Drupal\rest\LinkManager\RelationLinkManager');
   }
 }
