Index: src/wp-includes/post.php
===================================================================
--- src/wp-includes/post.php	(revision 42702)
+++ src/wp-includes/post.php	(working copy)
@@ -118,16 +118,19 @@
 
 	register_post_type(
 		'nav_menu_item', array(
-			'labels'           => array(
-				'name'          => __( 'Navigation Menu Items' ),
-				'singular_name' => __( 'Navigation Menu Item' ),
+			'labels'                => array(
+				'name'              => __( 'Navigation Menu Items' ),
+				'singular_name'     => __( 'Navigation Menu Item' ),
 			),
-			'public'           => false,
-			'_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
-			'hierarchical'     => false,
-			'rewrite'          => false,
-			'delete_with_user' => false,
-			'query_var'        => false,
+			'public'                => false,
+			'_builtin'              => true, /* internal use only. don't use this when registering your own post type. */
+			'hierarchical'          => false,
+			'rewrite'               => false,
+			'delete_with_user'      => false,
+			'query_var'             => false,
+			'show_in_rest'          => true,
+			'rest_base'             => 'menu-items',
+			'rest_controller_class' => 'WP_REST_Menu_Items_Controller',
 		)
 	);
 
Index: src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php
===================================================================
--- src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php	(nonexistent)
+++ src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php	(working copy)
@@ -0,0 +1,194 @@
+<?php
+/**
+ * REST API: WP_REST_Attachments_Controller class
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 4.7.0
+ */
+
+/**
+ * Core controller used to access attachments via the REST API.
+ *
+ * @since 4.7.0
+ *
+ * @see WP_REST_Posts_Controller
+ */
+class WP_REST_Menu_Items_Controller extends WP_REST_Posts_Controller {
+
+	/**
+	 * Registers the routes for the objects of the controller.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @see register_rest_route()
+	 */
+	public function register_routes() {
+
+		register_rest_route(
+			$this->namespace, '/' . $this->rest_base, array(
+				array(
+					'methods'             => WP_REST_Server::READABLE,
+					'callback'            => array( $this, 'get_items' ),
+					'permission_callback' => array( $this, 'get_items_permissions_check' ),
+					'args'                => $this->get_collection_params(),
+				),
+				'schema' => array( $this, 'get_public_item_schema' ),
+			)
+		);
+
+		$schema        = $this->get_item_schema();
+		$get_item_args = array(
+			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+		);
+		register_rest_route(
+			$this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
+				'args'   => array(
+					'id' => array(
+						'description' => __( 'Unique identifier for the object.' ),
+						'type'        => 'integer',
+					),
+				),
+				array(
+					'methods'             => WP_REST_Server::READABLE,
+					'callback'            => array( $this, 'get_item' ),
+					'permission_callback' => array( $this, 'get_item_permissions_check' ),
+					'args'                => $get_item_args,
+				),
+				'schema' => array( $this, 'get_public_item_schema' ),
+			)
+		);
+	}
+
+	/**
+	 * Prepares a single attachment output for response.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param WP_Post         $post    Attachment object.
+	 * @param WP_REST_Request $request Request object.
+	 * @return WP_REST_Response Response object.
+	 */
+	public function prepare_item_for_response( $post, $request ) {
+		$response = parent::prepare_item_for_response( $post, $request );
+		$data     = $response->get_data();
+
+		$menu_item = wp_setup_nav_menu_item( $post );
+
+		$data['id']               = (int) $menu_item->ID;
+		$data['menu_item_parent'] = (int) $menu_item->menu_item_parent;
+
+		if ( isset( $menu_item->object_id ) ) {
+			$data['object_id'] = (int) $menu_item->object_id;
+		}
+
+		$data['title']       = $menu_item->title;
+		$data['object']      = $menu_item->object;
+		$data['target']      = $menu_item->target;
+		$data['attr_title']  = $menu_item->attr_title;
+		$data['description'] = $menu_item->description;
+		$data['classes']     = $menu_item->classes;
+		$data['xfn']         = $menu_item->xfn;
+
+		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+
+		$data = $this->filter_response_by_context( $data, $context );
+
+		// Wrap the data in a response object.
+		$response = rest_ensure_response( $data );
+
+		$response->add_links( $this->prepare_links( $post ) );
+
+		/**
+		 * Filters an attachment returned from the REST API.
+		 *
+		 * Allows modification of the attachment right before it is returned.
+		 *
+		 * @since 4.7.0
+		 *
+		 * @param WP_REST_Response $response The response object.
+		 * @param WP_Post          $post     The original attachment post.
+		 * @param WP_REST_Request  $request  Request used to generate the response.
+		 */
+		return apply_filters( 'rest_prepare_nav_menu_item', $response, $post, $request );
+	}
+
+	/**
+	 * Retrieves the attachment's schema, conforming to JSON Schema.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @return array Item schema as an array.
+	 */
+	public function get_item_schema() {
+
+		$schema = parent::get_item_schema();
+
+		$schema['properties']['object'] = array(
+			'description' => __( 'The type of object originally represented, such as "category," "post", or "attachment."' ),
+			'type'        => 'string',
+			'context'     => array( 'view' ),
+		);
+
+		$schema['properties']['object_id'] = array(
+			'description' => __( 'The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories.' ),
+			'type'        => 'integer',
+			'context'     => array( 'view' ),
+		);
+
+		$schema['properties']['menu_item_parent'] = array(
+			'description' => __( 'The DB ID of the nav_menu_item that is this item\'s menu parent, if any. 0 otherwise.' ),
+			'type'        => 'integer',
+			'context'     => array( 'view' ),
+		);
+
+		$schema['properties']['menu_order'] = array(
+			'description' => __( 'The order of the object in relation to other object of its type.' ),
+			'type'        => 'integer',
+			'context'     => array( 'view', 'embed' ),
+		);
+
+		$schema['properties']['attr_title'] = array(
+			'description' => __( 'Text for the title attribute of the link element for this menu item.' ),
+			'type'        => 'string',
+			'context'     => array( 'view', 'embed' ),
+		);
+
+		$schema['properties']['classes'] = array(
+			'description' => __( 'Array of class attribute values for the link element of this menu item.' ),
+			'type'        => 'array',
+			'items'       => array(
+				'type' => 'string',
+			),
+			'context'     => array( 'view', 'embed' ),
+		);
+
+		$schema['properties']['target'] = array(
+			'description' => __( 'The target attribute of the link element for this menu item.' ),
+			'type'        => 'string',
+			'context'     => array( 'view', 'embed' ),
+		);
+
+		$schema['properties']['xfn'] = array(
+			'description' => __( 'The XFN relationship expressed in the link of this menu item.' ),
+			'type'        => 'string',
+			'context'     => array( 'view', 'embed' ),
+		);
+
+		$schema['properties']['description'] = array(
+			'description' => __( 'The description of this menu item.' ),
+			'type'        => 'string',
+			'context'     => array( 'view', 'embed' ),
+		);
+
+		$schema['properties']['link'] = array(
+			'description' => __( 'The URL to which this menu item points.' ),
+			'type'        => 'string',
+			'context'     => array( 'view', 'embed' ),
+		);
+
+		unset( $schema['properties']['password'] );
+
+		return $schema;
+	}
+}

Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php
===================================================================
--- src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php	(nonexistent)
+++ src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php	(working copy)
@@ -0,0 +1,269 @@
+<?php
+/**
+ * REST API: WP_REST_Menu_Locations_Controller class
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 4.x.x
+ */
+
+/**
+ * Core class used to access menu locations via the REST API.
+ *
+ * @since 4.x.x
+ *
+ * @see WP_REST_Controller
+ */
+class WP_REST_Menu_Locations_Controller extends WP_REST_Controller {
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 4.x.x
+	 */
+	public function __construct() {
+		$this->namespace = 'wp/v2';
+		$this->rest_base = 'menu-locations';
+	}
+
+	/**
+	 * Registers the routes for the objects of the controller.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @see register_rest_route()
+	 */
+	public function register_routes() {
+
+		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
+			array(
+				'methods'             => WP_REST_Server::READABLE,
+				'callback'            => array( $this, 'get_items' ),
+				'permission_callback' => array( $this, 'get_items_permissions_check' ),
+				'args'                => $this->get_collection_params(),
+			),
+			'schema' => array( $this, 'get_public_item_schema' ),
+		) );
+
+		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<location>[\w-]+)', array(
+			'args' => array(
+				'location' => array(
+					'description' => __( 'An alphanumeric identifier for the menu location.' ),
+					'type'        => 'string',
+				),
+			),
+			array(
+				'methods'             => WP_REST_Server::READABLE,
+				'callback'            => array( $this, 'get_item' ),
+				'permission_callback' => array( $this, 'get_item_permissions_check' ),
+				'args'                => array(
+					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+				),
+			),
+			'schema' => array( $this, 'get_public_item_schema' ),
+		) );
+	}
+
+	/**
+	 * Checks whether a given request has permission to read menu locations.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
+	 */
+	public function get_items_permissions_check( $request ) {
+		if ( 'edit' === $request['context'] ) {
+			$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
+
+			foreach ( $types as $type ) {
+				if ( current_user_can( $type->cap->edit_posts ) ) {
+					return true;
+				}
+			}
+			return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage menu locations.' ), array( 'status' => rest_authorization_required_code() ) );
+		}
+
+		return true;
+	}
+
+	/**
+	 * Retrieves all menu locations, depending on user context.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
+	 */
+	public function get_items( $request ) {
+		$data = array();
+
+		foreach ( get_registered_nav_menus() as $name => $description ) {
+			$location = new stdClass();
+			$location->name = $name;
+			$location->description = $description;
+
+			$location = $this->prepare_item_for_response( $location, $request );
+			$data[ $name ] = $this->prepare_response_for_collection( $location );
+		}
+
+		return rest_ensure_response( $data );
+	}
+
+	/**
+	 * Checks if a given request has access to read a menu location.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
+	 */
+	public function get_item_permissions_check( $request ) {
+		if ( ! array_key_exists( $request['location'], get_registered_nav_menus() ) ) {
+			return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) );
+		}
+
+		return true;
+	}
+
+	/**
+	 * Retrieves a specific menu location.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
+	 */
+	public function get_item( $request ) {
+		$registered_menus = get_registered_nav_menus();
+		if ( ! array_key_exists( $request['location'], $registered_menus ) ) {
+			return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) );
+		}
+
+		$location = new stdClass();
+		$location->name = $request['location'];
+		$location->description = $registered_menus[ $location->name ];
+
+		$data = $this->prepare_item_for_response( $location, $request );
+
+		return rest_ensure_response( $data );
+	}
+
+	/**
+	 * Prepares a menu location object for serialization.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @param stdClass        $location  Post status data.
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return WP_REST_Response Post status data.
+	 */
+	public function prepare_item_for_response( $location, $request ) {
+
+		$data = array(
+			'name'         => $location->name,
+			'description'  => $location->description,
+		);
+
+		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
+		$data = $this->add_additional_fields_to_object( $data, $request );
+		$data = $this->filter_response_by_context( $data, $context );
+
+		$response = rest_ensure_response( $data );
+
+		$response->add_links( $this->prepare_links( $location ) );
+
+		/**
+		 * Filters a menu location returned from the REST API.
+		 *
+		 * Allows modification of the menu location data right before it is
+		 * returned.
+		 *
+		 * @since 4.x.x
+		 *
+		 * @param WP_REST_Response $response The response object.
+		 * @param object           $location The original status object.
+		 * @param WP_REST_Request  $request  Request used to generate the response.
+		 */
+		return apply_filters( 'rest_prepare_menu_location', $response, $location, $request );
+	}
+
+	/**
+	 * Retrieves the menu location's schema, conforming to JSON Schema.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @return array Item schema data.
+	 */
+	public function get_item_schema() {
+		$schema = array(
+			'$schema'              => 'http://json-schema.org/draft-04/schema#',
+			'title'                => 'menu-location',
+			'type'                 => 'object',
+			'properties'           => array(
+				'name'             => array(
+					'description'  => __( 'The name of the menu location.' ),
+					'type'         => 'string',
+					'context'      => array( 'embed', 'view', 'edit' ),
+					'readonly'     => true,
+				),
+				'description'      => array(
+					'description'  => __( 'The description of the menu location.' ),
+					'type'         => 'string',
+					'context'      => array( 'embed', 'view', 'edit' ),
+					'readonly'     => true,
+				),
+			),
+		);
+
+		return $this->add_additional_fields_schema( $schema );
+	}
+
+	/**
+	 * Retrieves the query params for collections.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @return array Collection parameters.
+	 */
+	public function get_collection_params() {
+		return array(
+			'context' => $this->get_context_param( array( 'default' => 'view' ) ),
+		);
+	}
+
+	/**
+	 * Prepares links for the request.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @param stdClass $location Menu location.
+	 * @return array Links for the given menu location.
+	 */
+	protected function prepare_links( $location ) {
+		$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
+
+		// Entity meta.
+		$links = array(
+			'self' => array(
+				'href'   => rest_url( trailingslashit( $base ) . $location->name ),
+			),
+			'collection' => array(
+				'href'   => rest_url( $base ),
+			),
+		);
+
+		$menus = get_nav_menu_locations();
+		if ( array_key_exists( $location->name, $menus ) ) {
+			$id = $menus[ $location->name ];
+			$items_url = rest_url( "wp/v2/menus/${id}" );
+
+			$links['menus'] = array(
+				'href'       => $items_url,
+				'embeddable' => true,
+			);
+		}
+
+		return $links;
+	}
+}

Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php
===================================================================
--- src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php	(nonexistent)
+++ src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php	(working copy)
@@ -0,0 +1,47 @@
+<?php
+/**
+ * REST API: WP_REST_Menus_Controller class
+ *
+ * @package WordPress
+ * @subpackage REST_API
+ * @since 4.x.x
+ */
+
+/**
+ * Core class used to access menus via the REST API.
+ *
+ * @since 4.x.x
+ *
+ * @see WP_REST_Controller
+ */
+class WP_REST_Menus_Controller extends WP_REST_Terms_Controller {
+
+	/**
+	 * Prepares links for the request.
+	 *
+	 * @since 4.x.x
+	 *
+	 * @param object $term Term object.
+	 * @return array Links for the given term.
+	 */
+	protected function prepare_links( $term ) {
+		$links = parent::prepare_links( $term );
+
+		// Let's make sure that menu items are embeddable for a menu collection.
+		if ( array_key_exists( 'https://api.w.org/post_type', $links ) ) {
+			$post_type_links = $links['https://api.w.org/post_type'];
+
+			foreach ( $post_type_links as $index => $post_type_link ) {
+				if ( ! array_key_exists( 'href', $post_type_link ) || strpos( $post_type_link['href'], '/menu-items?' ) === false ) {
+					continue;
+				}
+
+				$post_type_links[ $index ]['embeddable'] = true;
+			}
+
+			$links['https://api.w.org/post_type'] = $post_type_links;
+		}
+
+		return $links;
+	}
+}

Property changes on: src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: src/wp-includes/rest-api.php
===================================================================
--- src/wp-includes/rest-api.php	(revision 42702)
+++ src/wp-includes/rest-api.php	(working copy)
@@ -233,6 +233,10 @@
 	// Settings.
 	$controller = new WP_REST_Settings_Controller;
 	$controller->register_routes();
+
+	// Menu location.
+	$controller = new WP_REST_Menu_Locations_Controller;
+	$controller->register_routes();
 }
 
 /**
Index: src/wp-includes/taxonomy.php
===================================================================
--- src/wp-includes/taxonomy.php	(revision 42702)
+++ src/wp-includes/taxonomy.php	(working copy)
@@ -101,17 +101,20 @@
 
 	register_taxonomy(
 		'nav_menu', 'nav_menu_item', array(
-			'public'            => false,
-			'hierarchical'      => false,
-			'labels'            => array(
-				'name'          => __( 'Navigation Menus' ),
-				'singular_name' => __( 'Navigation Menu' ),
+			'public'                => false,
+			'hierarchical'          => false,
+			'labels'                => array(
+				'name'              => __( 'Navigation Menus' ),
+				'singular_name'     => __( 'Navigation Menu' ),
 			),
-			'query_var'         => false,
-			'rewrite'           => false,
-			'show_ui'           => false,
-			'_builtin'          => true,
-			'show_in_nav_menus' => false,
+			'query_var'             => false,
+			'rewrite'               => false,
+			'show_ui'               => false,
+			'_builtin'              => true,
+			'show_in_nav_menus'     => false,
+			'show_in_rest'          => true,
+			'rest_base'             => 'menus',
+			'rest_controller_class' => 'WP_REST_Menus_Controller',
 		)
 	);
 
Index: src/wp-settings.php
===================================================================
--- src/wp-settings.php	(revision 42702)
+++ src/wp-settings.php	(working copy)
@@ -235,6 +235,9 @@
 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' );
 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-comments-controller.php' );
 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' );
+require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menu-items-controller.php' );
+require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menu-locations-controller.php' );
+require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menus-controller.php' );
 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' );
 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php' );
 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' );
