diff --git src/wp-includes/class-wp-oembed-controller.php src/wp-includes/class-wp-oembed-controller.php
index 22c062b..d2ab6c5 100644
--- src/wp-includes/class-wp-oembed-controller.php
+++ src/wp-includes/class-wp-oembed-controller.php
@@ -10,36 +10,18 @@
 /**
  * oEmbed API endpoint controller.
  *
- * Parses the oEmbed API requests and delivers
- * XML and JSON responses.
+ * Registers the API route and delivers the response data.
+ * The output format (XML or JSON) is handled by the REST API.
  *
  * @since 4.4.0
  */
 final class WP_oEmbed_Controller {
 	/**
-	 * Hook into the query parsing to detect oEmbed requests.
-	 *
-	 * If an oEmbed request is made, trigger the output.
+	 * Register the oEmbed REST API route.
 	 *
 	 * @since 4.4.0
-	 *
-	 * @param WP_Query $wp_query The WP_Query instance (passed by reference).
 	 */
-	public function parse_query( $wp_query ) {
-		if ( false === $wp_query->get( 'oembed', false ) ) {
-			return;
-		}
-
-		if ( false === $wp_query->get( 'url', false ) ) {
-			status_header( 400 );
-			echo 'URL parameter missing';
-			exit;
-		}
-
-		$url = esc_url_raw( get_query_var( 'url' ) );
-
-		$format = wp_oembed_ensure_format( get_query_var( 'format' ) );
-
+	public function register_routes() {
 		/**
 		 * Filter the maxwidth oEmbed parameter.
 		 *
@@ -48,30 +30,40 @@ final class WP_oEmbed_Controller {
 		 * @param int $maxwidth Maximum allowed width. Default 600.
 		 */
 		$maxwidth = apply_filters( 'oembed_default_width', 600 );
-		$maxwidth = absint( get_query_var( 'maxwidth', $maxwidth ) );
-
-		$callback = get_query_var( '_jsonp', false );
 
-		$request = array(
-			'url'      => $url,
-			'format'   => $format,
-			'maxwidth' => $maxwidth,
-			'callback' => $callback,
-		);
-
-		echo $this->dispatch( $request );
-		exit;
+		register_rest_route( 'oembed/1.0/', '/embed', array(
+			array(
+				'methods'  => WP_REST_Server::READABLE,
+				'callback' => array( $this, 'get_item' ),
+				'args'     => array(
+					'url'      => array(
+						'required'          => true,
+						'sanitize_callback' => 'esc_url_raw',
+					),
+					'format'   => array(
+						'default'           => 'json',
+						'sanitize_callback' => 'wp_oembed_ensure_format',
+					),
+					'maxwidth' => array(
+						'default'           => $maxwidth,
+						'sanitize_callback' => 'absint',
+					),
+				),
+			),
+		) );
 	}
 
 	/**
-	 * Handle the whole request and print the response.
+	 * Callback for the API endpoint.
+	 *
+	 * Returns the JSON object for the post.
 	 *
 	 * @since 4.4.0
 	 *
-	 * @param array $request The request arguments.
-	 * @return string The oEmbed API response.
+	 * @param WP_REST_Request $request Full data about the request.
+	 * @return WP_Error|WP_REST_Response
 	 */
-	public function dispatch( $request ) {
+	public function get_item( $request ) {
 		$post_id = url_to_postid( $request['url'] );
 
 		/**
@@ -84,81 +76,10 @@ final class WP_oEmbed_Controller {
 		 */
 		$post_id = apply_filters( 'oembed_request_post_id', $post_id, $request['url'] );
 
-		$data = get_oembed_response_data( $post_id, $request['maxwidth'] );
-
-		if ( false === $data ) {
-			status_header( 404 );
-			return __( 'Invalid URL.' );
-		}
-
-		if ( 'json' === $request['format'] ) {
-			return $this->json_response( $data, $request );
-		}
-
-		return $this->xml_response( $data );
-	}
-
-	/**
-	 * Print the oEmbed JSON response.
-	 *
-	 * @since 4.4.0
-	 *
-	 * @param array $data     The oEmbed response data.
-	 * @param array $request  The request arguments.
-	 * @return string The JSON response data.
-	 */
-	public function json_response( $data, $request ) {
-		if ( ! is_string( $request['callback'] ) || preg_match( '/[^\w\.]/', $request['callback'] ) ) {
-			$request['callback'] = false;
-		}
-
-		$result = wp_json_encode( $data );
-
-		// Bail if the result couldn't be JSON encoded.
-		if ( ! $result || ! is_array( $data ) || empty( $data ) ) {
-			status_header( 501 );
-			return 'Not implemented';
-		}
-
-		if ( ! headers_sent() ) {
-			$content_type = $request['callback'] ? 'application/javascript' : 'application/json';
-			header( 'Content-Type: ' . $content_type . '; charset=' . get_option( 'blog_charset' ) );
-			header( 'X-Content-Type-Options: nosniff' );
-		}
-
-		if ( $request['callback'] ) {
-			return '/**/' . $request['callback'] . '(' . $result . ')';
-		}
-
-		return $result;
-	}
-
-	/**
-	 * Print the oEmbed XML response.
-	 *
-	 * @since 4.4.0
-	 *
-	 * @param array $data The oEmbed response data.
-	 * @return string The XML response data.
-	 */
-	public function xml_response( $data ) {
-		if ( ! class_exists( 'SimpleXMLElement' ) ) {
-			status_header( 501 );
-			return get_status_header_desc( 501 );
-		}
-
-		$result = _oembed_create_xml( $data );
-
-		// Bail if there's no XML.
-		if ( ! $result ) {
-			status_header( 501 );
-			return get_status_header_desc( 501 );
-		}
-
-		if ( ! headers_sent() ) {
-			header( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ) );
+		if ( 0 === $post_id ) {
+			return new WP_Error( 'oembed_invalid_url', __( 'Invalid URL.' ), array( 'status' => 404 ) );
 		}
 
-		return $result;
+		return get_oembed_response_data( $post_id, $request['maxwidth'] );
 	}
 }
diff --git src/wp-includes/class-wp.php src/wp-includes/class-wp.php
index 0f86251..555167b 100644
--- src/wp-includes/class-wp.php
+++ src/wp-includes/class-wp.php
@@ -15,7 +15,7 @@ class WP {
 	 * @access public
 	 * @var array
 	 */
-	public $public_query_vars = array('m', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type', 'title', 'embed', 'oembed', 'format', 'url', '_jsonp', 'maxwidth' );
+	public $public_query_vars = array('m', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', 'pagename', 'page_id', 'error', 'comments_popup', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type', 'title', 'embed' );
 
 	/**
 	 * Private query variables.
diff --git src/wp-includes/default-filters.php src/wp-includes/default-filters.php
index bbe9f24..0b923aa 100644
--- src/wp-includes/default-filters.php
+++ src/wp-includes/default-filters.php
@@ -438,31 +438,32 @@ add_filter( 'image_send_to_editor', 'image_add_caption', 20, 8 );
 add_filter( 'media_send_to_editor', 'image_media_send_to_editor', 10, 3 );
 
 // Embeds
-add_action( 'parse_query',          'wp_oembed_parse_query'                );
-
-add_action( 'wp_head',              'wp_oembed_add_discovery_links'        );
-add_action( 'wp_head',              'wp_oembed_add_host_js'                );
-
-add_action( 'embed_head',           'print_emoji_detection_script'         );
-add_action( 'embed_head',           'print_emoji_styles'                   );
-add_action( 'embed_head',           'print_embed_styles'                   );
-add_action( 'embed_head',           'print_embed_scripts'                  );
-add_action( 'embed_head',           'wp_print_head_scripts',         20    );
-add_action( 'embed_head',           'wp_print_styles',               20    );
-add_action( 'embed_head',           'wp_no_robots'                         );
-add_action( 'embed_head',           'rel_canonical'                        );
-add_action( 'embed_head',           'locale_stylesheet'                    );
-
-add_action( 'embed_footer',         'wp_print_footer_scripts',       20    );
-
-add_filter( 'excerpt_more',         'wp_embed_excerpt_more',         20    );
-add_filter( 'the_excerpt_embed',    'wptexturize'                          );
-add_filter( 'the_excerpt_embed',    'convert_chars'                        );
-add_filter( 'the_excerpt_embed',    'wpautop'                              );
-add_filter( 'the_excerpt_embed',    'shortcode_unautop'                    );
-add_filter( 'the_excerpt_embed',    'wp_embed_excerpt_attachment'          );
-
-add_filter( 'oembed_dataparse',     'wp_filter_oembed_result',       10, 3 );
-add_filter( 'oembed_response_data', 'get_oembed_response_data_rich', 10, 4 );
+add_action( 'rest_api_init',          'wp_oembed_register_route'              );
+add_filter( 'rest_pre_serve_request', '_oembed_rest_pre_serve_request', 10, 4 );
+
+add_action( 'wp_head',                'wp_oembed_add_discovery_links'         );
+add_action( 'wp_head',                'wp_oembed_add_host_js'                 );
+
+add_action( 'embed_head',             'print_emoji_detection_script'          );
+add_action( 'embed_head',             'print_emoji_styles'                    );
+add_action( 'embed_head',             'print_embed_styles'                    );
+add_action( 'embed_head',             'print_embed_scripts'                   );
+add_action( 'embed_head',             'wp_print_head_scripts',          20    );
+add_action( 'embed_head',             'wp_print_styles',                20    );
+add_action( 'embed_head',             'wp_no_robots'                          );
+add_action( 'embed_head',             'rel_canonical'                         );
+add_action( 'embed_head',             'locale_stylesheet'                     );
+
+add_action( 'embed_footer',           'wp_print_footer_scripts',        20    );
+
+add_filter( 'excerpt_more',           'wp_embed_excerpt_more',          20    );
+add_filter( 'the_excerpt_embed',      'wptexturize'                           );
+add_filter( 'the_excerpt_embed',      'convert_chars'                         );
+add_filter( 'the_excerpt_embed',      'wpautop'                               );
+add_filter( 'the_excerpt_embed',      'shortcode_unautop'                     );
+add_filter( 'the_excerpt_embed',      'wp_embed_excerpt_attachment'           );
+
+add_filter( 'oembed_dataparse',       'wp_filter_oembed_result',        10, 3 );
+add_filter( 'oembed_response_data',   'get_oembed_response_data_rich',  10, 4 );
 
 unset( $filter, $action );
diff --git src/wp-includes/embed-functions.php src/wp-includes/embed-functions.php
index 35e6587..a6c0f44 100644
--- src/wp-includes/embed-functions.php
+++ src/wp-includes/embed-functions.php
@@ -328,17 +328,13 @@ function wp_embed_handler_video( $matches, $attr, $url, $rawattr ) {
 }
 
 /**
- * Parses an oEmbed API query.
+ * Registers the oEmbed REST API route.
  *
  * @since 4.4.0
- *
- * @see WP_oEmbed_Controller::parse_query()
- *
- * @param WP_Query $wp_query The current WP_Query instance.
  */
-function wp_oembed_parse_query( $wp_query ) {
+function wp_oembed_register_route() {
 	$controller = new WP_oEmbed_Controller();
-	$controller->parse_query( $wp_query );
+	$controller->register_routes();
 }
 
 /**
@@ -421,7 +417,7 @@ function get_post_embed_url( $post = null ) {
  * @return string The oEmbed endpoint URL.
  */
 function get_oembed_endpoint_url( $permalink = '', $format = 'json' ) {
-	$url = add_query_arg( array( 'oembed' => 'true' ), home_url( '/' ) );
+	$url = rest_url( 'oembed/1.0/embed' );
 
 	if ( 'json' === $format ) {
 		$format = false;
@@ -645,6 +641,56 @@ function wp_oembed_ensure_format( $format ) {
 }
 
 /**
+ * Hooks into the REST API output to print XML instead of JSON.
+ *
+ * This is only done for the oEmbed API endpoint,
+ * which supports both formats.
+ *
+ * @access private
+ * @since 4.4.0
+ *
+ * @param bool                      $served  Whether the request has already been served.
+ * @param WP_HTTP_ResponseInterface $result  Result to send to the client. Usually a WP_REST_Response.
+ * @param WP_REST_Request           $request Request used to generate the response.
+ * @param WP_REST_Server            $server  Server instance.
+ * @return true
+ */
+function _oembed_rest_pre_serve_request( $served, $result, $request, $server ) {
+	$params = $request->get_params();
+
+	if ( '/oembed/1.0/embed' !== $request->get_route() || 'GET' !== $request->get_method() ) {
+		return $served;
+	}
+
+	if ( ! isset( $params['format'] ) || 'xml' !== $params['format'] ) {
+		return $served;
+	}
+
+	// Embed links inside the request.
+	$data = $server->response_to_data( $result, false );
+
+	if ( 404 === $result->get_status() ) {
+		$data = $data[0];
+	}
+
+	$result = _oembed_create_xml( $data );
+
+	// Bail if there's no XML.
+	if ( ! $result ) {
+		status_header( 501 );
+		die( get_status_header_desc( 501 ) );
+	}
+
+	if ( ! headers_sent() ) {
+		$server->send_header( 'Content-Type', 'text/xml; charset=' . get_option( 'blog_charset' ) );
+	}
+
+	echo $result;
+
+	return true;
+}
+
+/**
  * Creates an XML string from a given array.
  *
  * @since 4.4.0
diff --git tests/phpunit/tests/oembed/controller.php tests/phpunit/tests/oembed/controller.php
index 34ce7dd..11be801 100644
--- tests/phpunit/tests/oembed/controller.php
+++ tests/phpunit/tests/oembed/controller.php
@@ -2,18 +2,132 @@
 
 /**
  * @group oembed
+ * @group restapi
  */
 class Test_oEmbed_Controller extends WP_UnitTestCase {
+	/**
+	 * @var WP_REST_Server
+	 */
+	protected $server;
+
+	public function setUp() {
+		parent::setUp();
+
+		/** @var WP_REST_Server $wp_rest_server */
+		global $wp_rest_server;
+		$this->server = $wp_rest_server = new Spy_REST_Server();
+
+		do_action( 'rest_api_init', $this->server );
+	}
+
+	function test_wp_oembed_ensure_format() {
+		$this->assertEquals( 'json', wp_oembed_ensure_format( 'json' ) );
+		$this->assertEquals( 'xml', wp_oembed_ensure_format( 'xml' ) );
+		$this->assertEquals( 'json', wp_oembed_ensure_format( 123 ) );
+		$this->assertEquals( 'json', wp_oembed_ensure_format( 'random' ) );
+		$this->assertEquals( 'json', wp_oembed_ensure_format( array() ) );
+	}
+
+	function test_oembed_create_xml() {
+		$actual = _oembed_create_xml( array(
+			'foo'  => 'bar',
+			'bar'  => 'baz',
+			'ping' => 'pong',
+		) );
+
+		$expected = '<oembed><foo>bar</foo><bar>baz</bar><ping>pong</ping></oembed>';
+
+		$this->assertStringEndsWith( $expected, trim( $actual ) );
+
+		$actual = _oembed_create_xml( array(
+			'foo'  => array(
+				'bar' => 'baz',
+			),
+			'ping' => 'pong',
+		) );
+
+		$expected = '<oembed><foo><bar>baz</bar></foo><ping>pong</ping></oembed>';
+
+		$this->assertStringEndsWith( $expected, trim( $actual ) );
+
+		$actual = _oembed_create_xml( array(
+			'foo'   => array(
+				'bar' => array(
+					'ping' => 'pong',
+				),
+			),
+			'hello' => 'world',
+		) );
+
+		$expected = '<oembed><foo><bar><ping>pong</ping></bar></foo><hello>world</hello></oembed>';
+
+		$this->assertStringEndsWith( $expected, trim( $actual ) );
+
+		$actual = _oembed_create_xml( array(
+			array(
+				'foo' => array(
+					'bar',
+				),
+			),
+			'helloworld',
+		) );
+
+		$expected = '<oembed><oembed><foo><oembed>bar</oembed></foo></oembed><oembed>helloworld</oembed></oembed>';
+
+		$this->assertStringEndsWith( $expected, trim( $actual ) );
+	}
+
+	public function test_route_availability() {
+		// Check the route was registered correctly.
+		$filtered_routes = $this->server->get_routes();
+		$this->assertArrayHasKey( '/oembed/1.0/embed', $filtered_routes );
+		$route = $filtered_routes['/oembed/1.0/embed'];
+		$this->assertCount( 1, $route );
+		$this->assertArrayHasKey( 'callback', $route[0] );
+		$this->assertArrayHasKey( 'methods', $route[0] );
+		$this->assertArrayHasKey( 'args', $route[0] );
+	}
+
+	function test_request_with_wrong_method() {
+		$request = new WP_REST_Request( 'POST', '/oembed/1.0/embed' );
+
+		$response = $this->server->dispatch( $request );
+		$data     = $response->get_data();
+
+		$this->assertEquals( 'rest_no_route', $data[0]['code'] );
+	}
+
+	function test_request_without_url_param() {
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+
+		$response = $this->server->dispatch( $request );
+		$data     = $response->get_data();
+
+		$this->assertEquals( 'rest_missing_callback_param', $data[0]['code'] );
+		$this->assertEquals( 'url', $data[0]['data']['params'][0] );
+	}
+
 	function test_request_with_bad_url() {
-		$request = array(
-			'url'      => '',
-			'format'   => 'json',
-			'maxwidth' => 600,
-		);
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+		$request->set_param( 'url', 'http://google.com/' );
 
-		$legacy_controller = new WP_oEmbed_Controller();
+		$response = $this->server->dispatch( $request );
+		$data     = $response->get_data();
 
-		$this->assertEquals( 'Invalid URL.', $legacy_controller->dispatch( $request ) );
+		$this->assertEquals( 'oembed_invalid_url', $data[0]['code'] );
+	}
+
+	function test_request_invalid_format() {
+		$post_id = $this->factory()->post->create();
+
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+		$request->set_param( 'url', get_permalink( $post_id ) );
+		$request->set_param( 'format', 'random' );
+
+		$response = $this->server->dispatch( $request );
+		$data     = $response->get_data();
+
+		$this->assertTrue( is_array( $data ) );
 	}
 
 	function test_request_json() {
@@ -25,18 +139,12 @@ class Test_oEmbed_Controller extends WP_UnitTestCase {
 			'post_title'  => 'Hello World',
 		) );
 
-		// WP_Query arguments.
-		$request = array(
-			'url'	  => get_permalink( $post->ID ),
-			'format'   => 'json',
-			'maxwidth' => 400,
-			'callback' => '',
-			'oembed'   => true,
-		);
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+		$request->set_param( 'url', get_permalink( $post->ID ) );
+		$request->set_param( 'maxwidth', 400 );
 
-		$legacy_controller = new WP_oEmbed_Controller();
-
-		$data = json_decode( $legacy_controller->dispatch( $request ), true );
+		$response = $this->server->dispatch( $request );
+		$data     = $response->get_data();
 
 		$this->assertTrue( is_array( $data ) );
 
@@ -59,64 +167,6 @@ class Test_oEmbed_Controller extends WP_UnitTestCase {
 		$this->assertTrue( $data['width'] <= $request['maxwidth'] );
 	}
 
-	function test_request_jsonp() {
-		$user = self::factory()->user->create_and_get( array(
-			'display_name' => 'John Doe',
-		) );
-		$post = self::factory()->post->create_and_get( array(
-			'post_author' => $user->ID,
-			'post_title'  => 'Hello World',
-		) );
-
-		$request = array(
-			'url'	  => get_permalink( $post->ID ),
-			'format'   => 'json',
-			'maxwidth' => 600,
-			'callback' => 'mycallback',
-		);
-
-		$legacy_controller = new WP_oEmbed_Controller();
-
-		$data = $legacy_controller->dispatch( $request );
-
-		$this->assertEquals( 0, strpos( $data, '/**/mycallback(' ) );
-	}
-
-	function test_request_jsonp_invalid_callback() {
-		$user = self::factory()->user->create_and_get( array(
-			'display_name' => 'John Doe',
-		) );
-		$post = self::factory()->post->create_and_get( array(
-			'post_author' => $user->ID,
-			'post_title'  => 'Hello World',
-		) );
-
-		$request = array(
-			'url'	  => get_permalink( $post->ID ),
-			'format'   => 'json',
-			'maxwidth' => 600,
-			'callback' => array( 'foo', 'bar' ),
-		);
-
-		$legacy_controller = new WP_oEmbed_Controller();
-
-		$data = $legacy_controller->dispatch( $request );
-
-		$this->assertFalse( strpos( $data, '/**/' ) );
-	}
-
-	function test_request_json_invalid_data() {
-		$request = array(
-			'callback' => '',
-		);
-
-		$legacy_controller = new WP_oEmbed_Controller();
-
-		$this->assertEquals( 'Not implemented',  $legacy_controller->json_response( null, $request ) );
-		$this->assertEquals( 'Not implemented',  $legacy_controller->json_response( 123, $request ) );
-		$this->assertEquals( 'Not implemented',  $legacy_controller->json_response( array(), $request ) );
-	}
-
 	function test_request_xml() {
 		$user = self::factory()->user->create_and_get( array(
 			'display_name' => 'John Doe',
@@ -126,21 +176,15 @@ class Test_oEmbed_Controller extends WP_UnitTestCase {
 			'post_title'  => 'Hello World',
 		) );
 
-		$request = array(
-			'url'	  => get_permalink( $post->ID ),
-			'format'   => 'xml',
-			'maxwidth' => 400,
-			'callback' => '',
-		);
-
-		$legacy_controller = new WP_oEmbed_Controller();
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+		$request->set_param( 'url', get_permalink( $post->ID ) );
+		$request->set_param( 'format', 'xml' );
+		$request->set_param( 'maxwidth', 400 );
 
-		$data = $legacy_controller->dispatch( $request );
+		$response = $this->server->dispatch( $request );
+		$data     = $response->get_data();
 
-		$data = simplexml_load_string( $data );
-		$this->assertInstanceOf( 'SimpleXMLElement', $data );
-
-		$data = (array) $data;
+		$this->assertTrue( is_array( $data ) );
 
 		$this->assertArrayHasKey( 'version', $data );
 		$this->assertArrayHasKey( 'provider_name', $data );
@@ -161,14 +205,6 @@ class Test_oEmbed_Controller extends WP_UnitTestCase {
 		$this->assertTrue( $data['width'] <= $request['maxwidth'] );
 	}
 
-	function test_request_xml_invalid_data() {
-		$legacy_controller = new WP_oEmbed_Controller();
-
-		$this->assertEquals( get_status_header_desc( 501 ),  $legacy_controller->xml_response( null ) );
-		$this->assertEquals( get_status_header_desc( 501 ),  $legacy_controller->xml_response( 123 ) );
-		$this->assertEquals( get_status_header_desc( 501 ),  $legacy_controller->xml_response( array() ) );
-	}
-
 	/**
 	 * @group multisite
 	 */
@@ -202,73 +238,79 @@ class Test_oEmbed_Controller extends WP_UnitTestCase {
 		restore_current_blog();
 	}
 
-	function test_get_oembed_endpoint_url() {
-		$this->assertEquals( home_url() . '/?oembed=true', get_oembed_endpoint_url() );
-		$this->assertEquals( home_url() . '/?oembed=true', get_oembed_endpoint_url( '', 'json' ) );
-		$this->assertEquals( home_url() . '/?oembed=true', get_oembed_endpoint_url( '', 'xml' ) );
+	function test_rest_pre_serve_request() {
+		$user = $this->factory()->user->create_and_get( array(
+			'display_name' => 'John Doe',
+		) );
+		$post = $this->factory()->post->create_and_get( array(
+			'post_author' => $user->ID,
+			'post_title'  => 'Hello World',
+		) );
 
-		$post_id     = self::factory()->post->create();
-		$url         = get_permalink( $post_id );
-		$url_encoded = urlencode( $url );
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+		$request->set_param( 'url', get_permalink( $post->ID ) );
+		$request->set_param( 'format', 'xml' );
+
+		$response = $this->server->dispatch( $request );
+
+		ob_start();
+		_oembed_rest_pre_serve_request( true, $response, $request, $this->server );
+		$output = ob_get_clean();
 
-		$this->assertEquals( home_url() . '/?oembed=true&url=' . $url_encoded, get_oembed_endpoint_url( $url ) );
-		$this->assertEquals( home_url() . '/?oembed=true&url=' . $url_encoded . '&format=xml', get_oembed_endpoint_url( $url, 'xml' ) );
+		$xml = simplexml_load_string( $output );
+		$this->assertInstanceOf( 'SimpleXMLElement', $xml );
 	}
 
-	function test_wp_oembed_ensure_format() {
-		$this->assertEquals( 'json', wp_oembed_ensure_format( 'json' ) );
-		$this->assertEquals( 'xml', wp_oembed_ensure_format( 'xml' ) );
-		$this->assertEquals( 'json', wp_oembed_ensure_format( 123 ) );
-		$this->assertEquals( 'json', wp_oembed_ensure_format( 'random' ) );
-		$this->assertEquals( 'json', wp_oembed_ensure_format( array() ) );
+	function test_rest_pre_serve_request_wrong_format() {
+		$post = $this->factory()->post->create_and_get();
+
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+		$request->set_param( 'url', get_permalink( $post->ID ) );
+		$request->set_param( 'format', 'json' );
+
+		$response = $this->server->dispatch( $request );
+
+		$this->assertTrue( _oembed_rest_pre_serve_request( true, $response, $request, $this->server ) );
 	}
 
-	function test_oembed_create_xml() {
-		$actual = _oembed_create_xml( array(
-			'foo'  => 'bar',
-			'bar'  => 'baz',
-			'ping' => 'pong',
-		) );
+	function test_rest_pre_serve_request_wrong_method() {
+		$post = $this->factory()->post->create_and_get();
 
-		$expected = '<oembed><foo>bar</foo><bar>baz</bar><ping>pong</ping></oembed>';
+		$request = new WP_REST_Request( 'HEAD', '/oembed/1.0/embed' );
+		$request->set_param( 'url', get_permalink( $post->ID ) );
+		$request->set_param( 'format', 'xml' );
 
-		$this->assertStringEndsWith( $expected, trim( $actual ) );
+		$response = $this->server->dispatch( $request );
 
-		$actual = _oembed_create_xml( array(
-			'foo'  => array(
-				'bar' => 'baz',
-			),
-			'ping' => 'pong',
-		) );
+		$this->assertTrue( _oembed_rest_pre_serve_request( true, $response, $request, $this->server ) );
+	}
 
-		$expected = '<oembed><foo><bar>baz</bar></foo><ping>pong</ping></oembed>';
+	function test_get_oembed_endpoint_url() {
+		$this->assertEquals( home_url() . '/?rest_route=/oembed/1.0/embed', get_oembed_endpoint_url() );
+		$this->assertEquals( home_url() . '/?rest_route=/oembed/1.0/embed', get_oembed_endpoint_url( '', 'json' ) );
+		$this->assertEquals( home_url() . '/?rest_route=/oembed/1.0/embed', get_oembed_endpoint_url( '', 'xml' ) );
 
-		$this->assertStringEndsWith( $expected, trim( $actual ) );
+		$post_id     = $this->factory()->post->create();
+		$url         = get_permalink( $post_id );
+		$url_encoded = urlencode( $url );
 
-		$actual = _oembed_create_xml( array(
-			'foo'   => array(
-				'bar' => array(
-					'ping' => 'pong',
-				),
-			),
-			'hello' => 'world',
-		) );
+		$this->assertEquals( home_url() . '/?rest_route=%2Foembed%2F1.0%2Fembed&url=' . $url_encoded, get_oembed_endpoint_url( $url ) );
+		$this->assertEquals( home_url() . '/?rest_route=%2Foembed%2F1.0%2Fembed&url=' . $url_encoded . '&format=xml', get_oembed_endpoint_url( $url, 'xml' ) );
+	}
 
-		$expected = '<oembed><foo><bar><ping>pong</ping></bar></foo><hello>world</hello></oembed>';
+	function test_get_oembed_endpoint_url_pretty_permalinks() {
+		update_option( 'permalink_structure', '/%postname%' );
 
-		$this->assertStringEndsWith( $expected, trim( $actual ) );
+		$this->assertEquals( home_url() . '/wp-json/oembed/1.0/embed', get_oembed_endpoint_url() );
+		$this->assertEquals( home_url() . '/wp-json/oembed/1.0/embed', get_oembed_endpoint_url( '', 'xml' ) );
 
-		$actual = _oembed_create_xml( array(
-			array(
-				'foo' => array(
-					'bar',
-				),
-			),
-			'helloworld',
-		) );
+		$post_id     = $this->factory()->post->create();
+		$url         = get_permalink( $post_id );
+		$url_encoded = urlencode( $url );
 
-		$expected = '<oembed><oembed><foo><oembed>bar</oembed></foo></oembed><oembed>helloworld</oembed></oembed>';
+		$this->assertEquals( home_url() . '/wp-json/oembed/1.0/embed?url=' . $url_encoded, get_oembed_endpoint_url( $url ) );
+		$this->assertEquals( home_url() . '/wp-json/oembed/1.0/embed?url=' . $url_encoded . '&format=xml', get_oembed_endpoint_url( $url, 'xml' ) );
 
-		$this->assertStringEndsWith( $expected, trim( $actual ) );
+		update_option( 'permalink_structure', '' );
 	}
 }
diff --git tests/phpunit/tests/oembed/headers.php tests/phpunit/tests/oembed/headers.php
index 3c9f80f..07a657b 100644
--- tests/phpunit/tests/oembed/headers.php
+++ tests/phpunit/tests/oembed/headers.php
@@ -3,64 +3,36 @@
 /**
  * @runTestsInSeparateProcesses
  * @preserveGlobalState disabled
- * @group oembed
- * @group oembed-headers
+ * @group               oembed
+ * @group               oembed-headers
  */
 class Tests_oEmbed_HTTP_Headers extends WP_UnitTestCase {
-	function test_request_json_response_headers() {
+	function test_rest_pre_serve_request_headers() {
 		if ( ! function_exists( 'xdebug_get_headers' ) ) {
 			$this->markTestSkipped( 'xdebug is required for this test' );
 		}
 
-		$post = self::factory()->post->create_and_get( array(
+		$post = $this->factory()->post->create_and_get( array(
 			'post_title'  => 'Hello World',
 		) );
 
-		$request = array(
-			'url'      => get_permalink( $post->ID ),
-			'format'   => 'json',
-			'maxwidth' => 600,
-			'callback' => '',
-		);
+		$request = new WP_REST_Request( 'GET', '/oembed/1.0/embed' );
+		$request->set_param( 'url', get_permalink( $post->ID ) );
+		$request->set_param( 'format', 'xml' );
 
-		$legacy_controller = new WP_oEmbed_Controller();
-		$legacy_controller->dispatch( $request );
+		$server = new WP_REST_Server();
 
-		$headers = xdebug_get_headers();
-
-		$this->assertTrue( in_array( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ), $headers ) );
-		$this->assertTrue( in_array( 'X-Content-Type-Options: nosniff', $headers ) );
-
-		$request['callback'] = 'foobar';
-
-		$legacy_controller->dispatch( $request );
-
-		$headers = xdebug_get_headers();
-
-		$this->assertTrue( in_array( 'Content-Type: application/javascript; charset=' . get_option( 'blog_charset' ), $headers ) );
-		$this->assertTrue( in_array( 'X-Content-Type-Options: nosniff', $headers ) );
-	}
+		$response = $server->dispatch( $request );
 
-	function test_request_xml_response_headers() {
-		if ( ! function_exists( 'xdebug_get_headers' ) ) {
-			$this->markTestSkipped( 'xdebug is required for this test' );
-		}
-
-		$post = self::factory()->post->create_and_get( array(
-			'post_title'  => 'Hello World',
-		) );
+		ob_start();
+		_oembed_rest_pre_serve_request( true, $response, $request, $server );
+		$output = ob_get_clean();
 
-		$request = array(
-			'url'      => get_permalink( $post->ID ),
-			'format'   => 'xml',
-			'maxwidth' => 600,
-		);
-
-		$legacy_controller = new WP_oEmbed_Controller();
-		$legacy_controller->dispatch( $request );
+		$this->assertNotEmpty( $output );
 
 		$headers = xdebug_get_headers();
 
 		$this->assertTrue( in_array( 'Content-Type: text/xml; charset=' . get_option( 'blog_charset' ), $headers ) );
 	}
+
 }
