Index: src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php
===================================================================
--- src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php	(revision 46814)
+++ src/wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php	(working copy)
@@ -41,20 +41,45 @@
 			array(
 				array(
 					'methods'             => WP_REST_Server::READABLE,
-					'callback'            => array( $this, 'get_item' ),
+					'callback'            => array( $this, 'get_items' ),
 					'args'                => array(),
-					'permission_callback' => array( $this, 'get_item_permissions_check' ),
+					'permission_callback' => array( $this, 'get_items_permissions_check' ),
 				),
 				array(
 					'methods'             => WP_REST_Server::EDITABLE,
-					'callback'            => array( $this, 'update_item' ),
+					'callback'            => array( $this, 'update_items' ),
 					'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
+					'permission_callback' => array( $this, 'get_items_permissions_check' ),
+				),
+				'schema' => array( $this, 'get_public_item_schema' ),
+			)
+		);
+
+		register_rest_route(
+			$this->namespace,
+			'/' . $this->rest_base . '/(?P<option>.+)',
+			array(
+				'args'   => array(
+					'id' => array(
+						'description' => __( 'The option to fetch.' ),
+						'type'        => 'string',
+					),
+				),
+				array(
+					'methods'             => WP_REST_Server::READABLE,
+					'callback'            => array( $this, 'get_item' ),
 					'permission_callback' => array( $this, 'get_item_permissions_check' ),
+					'args'                => array(),
 				),
+				array(
+					'methods'             => WP_REST_Server::EDITABLE,
+					'callback'            => array( $this, 'update_item' ),
+					'permission_callback' => array( $this, 'update_item_permissions_check' ),
+					'args'                => array(),
+				),
 				'schema' => array( $this, 'get_public_item_schema' ),
 			)
 		);
-
 	}
 
 	/**
@@ -65,11 +90,68 @@
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return bool True if the request has read access for the item, otherwise false.
 	 */
+	public function get_items_permissions_check( $request ) {
+		return true;
+		return current_user_can( 'manage_options' );
+	}
+
+	/**
+	 * Checks if a given request has access to read a setting.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return bool True if the request has read access for the item, otherwise false.
+	 */
 	public function get_item_permissions_check( $request ) {
+		// to make testing easier
+		return true;
+		return current_user_can( 'edit_posts' );
+	}
+
+	/**
+	 * Checks if a given request has access to update a setting.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return bool True if the request has update access for the item, otherwise false.
+	 */
+	public function update_item_permissions_check( $request ) {
 		return current_user_can( 'manage_options' );
 	}
 
+
 	/**
+	 * Filters the value of a setting recognized by the REST API.
+	 *
+	 * Allow hijacking the setting value and overriding the built-in behavior by returning a
+	 * non-null value.  The returned value will be presented as the setting value instead.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param mixed  $result Value to use for the requested setting. Can be a scalar
+	 *                       matching the registered schema for the setting, or null to
+	 *                       follow the default get_option() behavior.
+	 * @param string $name   Setting name (as shown in REST API responses).
+	 * @param array  $args   Arguments passed to register_setting() for this setting.
+	 */
+	private function get_filtered_setting( $name, $args ) {
+		$response = apply_filters( 'rest_pre_get_setting', null, $name, $args );
+
+		if ( is_null( $response) ) {
+			// Default to a null value as "null" in the response means "not set".
+			$response = get_option( $args['option_name'], $args['schema']['default'] );
+		}
+
+		/*
+			* Because get_option() is lossy, we have to
+			* cast values to the type they are registered with.
+			*/
+		return $this->prepare_value( $response, $args['schema'] );
+	}
+
+	/**
 	 * Retrieves the settings.
 	 *
 	 * @since 4.7.0
@@ -77,37 +159,12 @@
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return array|WP_Error Array on success, or WP_Error object on failure.
 	 */
-	public function get_item( $request ) {
+	public function get_items( $request ) {
 		$options  = $this->get_registered_options();
 		$response = array();
 
 		foreach ( $options as $name => $args ) {
-			/**
-			 * Filters the value of a setting recognized by the REST API.
-			 *
-			 * Allow hijacking the setting value and overriding the built-in behavior by returning a
-			 * non-null value.  The returned value will be presented as the setting value instead.
-			 *
-			 * @since 4.7.0
-			 *
-			 * @param mixed  $result Value to use for the requested setting. Can be a scalar
-			 *                       matching the registered schema for the setting, or null to
-			 *                       follow the default get_option() behavior.
-			 * @param string $name   Setting name (as shown in REST API responses).
-			 * @param array  $args   Arguments passed to register_setting() for this setting.
-			 */
-			$response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args );
-
-			if ( is_null( $response[ $name ] ) ) {
-				// Default to a null value as "null" in the response means "not set".
-				$response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] );
-			}
-
-			/*
-			 * Because get_option() is lossy, we have to
-			 * cast values to the type they are registered with.
-			 */
-			$response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] );
+			$response[ $name ] = $this->get_filtered_setting( $name, $args );
 		}
 
 		return $response;
@@ -135,6 +192,59 @@
 	}
 
 	/**
+	 * Filters whether to preempt a setting value update.
+	 *
+	 * Allows hijacking the setting update logic and overriding the built-in behavior by
+	 * returning true.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param bool   $result Whether to override the default behavior for updating the
+	 *                       value of a setting.
+	 * @param string $name   Setting name (as shown in REST API responses).
+	 * @param mixed  $value  Updated setting value.
+	 * @param array  $args   Arguments passed to register_setting() for this setting.
+	 */
+	private function filtered_update_option( $name, $value, $args ) {
+		$updated = apply_filters( 'rest_pre_update_setting', false, $name, $value, $args );
+
+		if ( $updated ) {
+			return;
+		}
+
+		/*
+		* A null value for an option would have the same effect as
+		* deleting the option from the database, and relying on the
+		* default value.
+		*/
+		if ( is_null( $value ) ) {
+			/*
+			* A null value is returned in the response for any option
+			* that has a non-scalar value.
+			*
+			* To protect clients from accidentally including the null
+			* values from a response object in a request, we do not allow
+			* options with values that don't pass validation to be updated to null.
+			* Without this added protection a client could mistakenly
+			* delete all options that have invalid values from the
+			* database.
+			*/
+			if ( is_wp_error( rest_validate_value_from_schema( get_option( $args['option_name'], false ), $args['schema'] ) ) ) {
+				return new WP_Error(
+					'rest_invalid_stored_value',
+					/* translators: %s: Property name. */
+					sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
+					array( 'status' => 500 )
+				);
+			}
+
+			delete_option( $args['option_name'] );
+		} else {
+			update_option( $args['option_name'], $value );
+		}
+	}
+
+	/**
 	 * Updates settings for the settings object.
 	 *
 	 * @since 4.7.0
@@ -142,7 +252,7 @@
 	 * @param WP_REST_Request $request Full details about the request.
 	 * @return array|WP_Error Array on success, or error object on failure.
 	 */
-	public function update_item( $request ) {
+	public function update_items( $request ) {
 		$options = $this->get_registered_options();
 
 		$params = $request->get_params();
@@ -152,59 +262,10 @@
 				continue;
 			}
 
-			/**
-			 * Filters whether to preempt a setting value update.
-			 *
-			 * Allows hijacking the setting update logic and overriding the built-in behavior by
-			 * returning true.
-			 *
-			 * @since 4.7.0
-			 *
-			 * @param bool   $result Whether to override the default behavior for updating the
-			 *                       value of a setting.
-			 * @param string $name   Setting name (as shown in REST API responses).
-			 * @param mixed  $value  Updated setting value.
-			 * @param array  $args   Arguments passed to register_setting() for this setting.
-			 */
-			$updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args );
-
-			if ( $updated ) {
-				continue;
-			}
-
-			/*
-			 * A null value for an option would have the same effect as
-			 * deleting the option from the database, and relying on the
-			 * default value.
-			 */
-			if ( is_null( $request[ $name ] ) ) {
-				/*
-				 * A null value is returned in the response for any option
-				 * that has a non-scalar value.
-				 *
-				 * To protect clients from accidentally including the null
-				 * values from a response object in a request, we do not allow
-				 * options with values that don't pass validation to be updated to null.
-				 * Without this added protection a client could mistakenly
-				 * delete all options that have invalid values from the
-				 * database.
-				 */
-				if ( is_wp_error( rest_validate_value_from_schema( get_option( $args['option_name'], false ), $args['schema'] ) ) ) {
-					return new WP_Error(
-						'rest_invalid_stored_value',
-						/* translators: %s: Property name. */
-						sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
-						array( 'status' => 500 )
-					);
-				}
-
-				delete_option( $args['option_name'] );
-			} else {
-				update_option( $args['option_name'], $request[ $name ] );
-			}
+			$this->filtered_update_option( $name, $request[ $name ], $args );
 		}
 
-		return $this->get_item( $request );
+		return $this->get_items( $request );
 	}
 
 	/**
@@ -345,4 +406,57 @@
 
 		return $schema;
 	}
+
+	/**
+	 * Retrieves a single option
+	 *
+	 * @since ?
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return array|WP_Error Array on success, or WP_Error object on failure.
+	 */
+	public function get_item( $request ) {
+		$name = $request['option'];
+		$options  = $this->get_registered_options();
+		if ( ! isset( $options[ $name ] ) ) {
+			return new WP_Error(
+				'rest_invalid_setting',
+				/* translators: %s: Settings name. */
+				sprintf( __( '%s is not a registered setting.' ), $name ),
+				array( 'status' => 500 )
+			);
+		}
+
+		$response = array();
+		$response[ $name ] = $this->get_filtered_setting( $name, $options[ $name ] );
+		return $response;
+	}
+
+	/**
+	 * Updates a given setting
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param WP_REST_Request $request Full details about the request.
+	 * @return array|WP_Error Array on success, or error object on failure.
+	 */
+	public function update_item( $request ) {
+		$name = $request['option'];
+		$options = $this->get_registered_options();
+
+		$params = $request->get_params();
+
+		if ( ! array_key_exists( $name, $params ) ) {
+			return new WP_Error(
+				'rest_invalid_setting',
+				/* translators: %s: Settings name. */
+				sprintf( __( '%s is not a registered setting.' ), $name ),
+				array( 'status' => 500 )
+			);
+		}
+
+		$this->filtered_update_option( $name, $request[ $name ], $options[ $name ] );
+
+		return $this->get_item( $request );
+	}
 }
