diff --git src/wp-includes/embed.php src/wp-includes/embed.php
index 8c79cc8..304bc3d 100644
--- src/wp-includes/embed.php
+++ src/wp-includes/embed.php
@@ -341,11 +341,12 @@ function wp_oembed_register_route() {
  * Adds oEmbed discovery links in the website <head>.
  *
  * @since 4.4.0
+ * @since x.x.x Output was adjusted to only embed if the post supports it.
  */
 function wp_oembed_add_discovery_links() {
 	$output = '';
 
-	if ( is_singular() ) {
+	if ( is_singular() && is_post_embeddable() ) {
 		$output .= '<link rel="alternate" type="application/json+oembed" href="' . esc_url( get_oembed_endpoint_url( get_permalink() ) ) . '" />' . "\n";
 
 		if ( class_exists( 'SimpleXMLElement' ) ) {
@@ -510,10 +511,11 @@ JS;
  * Retrieves the oEmbed response data for a given post.
  *
  * @since 4.4.0
+ * @since x.x.x Output was adjusted to only embed if the post type supports it.
  *
- * @param WP_Post|int $post  Post object or ID.
- * @param int         $width The requested width.
- * @return array|false Response data on success, false if post doesn't exist.
+ * @param WP_Post|int  $post  Post object or ID.
+ * @param int          $width The requested width.
+ * @return array|false Response data on success, false if post doesn't exist, or if post is not embeddable.
  */
 function get_oembed_response_data( $post, $width ) {
 	$post = get_post( $post );
@@ -526,6 +528,10 @@ function get_oembed_response_data( $post, $width ) {
 		return false;
 	}
 
+	if ( ! is_post_embeddable( $post ) ) {
+		return false;
+	}
+
 	/**
 	 * Filter the allowed minimum and maximum widths for the oEmbed response.
 	 *
diff --git src/wp-includes/post.php src/wp-includes/post.php
index be08ae3..74ae1e2 100644
--- src/wp-includes/post.php
+++ src/wp-includes/post.php
@@ -890,6 +890,7 @@ function get_post_types( $args = array(), $output = 'names', $operator = 'and' )
  * @since 2.9.0
  * @since 3.0.0 The `show_ui` argument is now enforced on the new post screen.
  * @since 4.4.0 The `show_ui` argument is now enforced on the post type listing screen and post editing screen.
+ * @since x.x.x Introduced the `is_embeddable` argument.
  *
  * @global array      $wp_post_types List of post types.
  * @global WP_Rewrite $wp_rewrite    Used for default feeds.
@@ -962,6 +963,8 @@ function get_post_types( $args = array(), $output = 'names', $operator = 'and' )
  *     @type bool|string $has_archive          Whether there should be post type archives, or if a string, the
  *                                             archive slug to use. Will generate the proper rewrite rules if
  *                                             $rewrite is enabled. Default false.
+ *     @type bool        $is_embeddable        Whether this post type should be embeddable through oEmbed.
+ *                                             Default is value of `$public`.
  *     @type bool|array  $rewrite              {
  *         Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
  *         Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
@@ -1039,6 +1042,7 @@ function register_post_type( $post_type, $args = array() ) {
 		'register_meta_box_cb' => null,
 		'taxonomies'           => array(),
 		'has_archive'          => false,
+		'is_embeddable'        => null,
 		'rewrite'              => true,
 		'query_var'            => true,
 		'can_export'           => true,
@@ -1163,6 +1167,11 @@ function register_post_type( $post_type, $args = array() ) {
 		add_permastruct( $post_type, "{$args->rewrite['slug']}/%$post_type%", $permastruct_args );
 	}
 
+	// If not set, default to the setting for public.
+	if ( null === $args->is_embeddable ) {
+		$args->is_embeddable = $args->public;
+	}
+
 	// Register the post type meta box if a custom callback was specified.
 	if ( $args->register_meta_box_cb )
 		add_action( 'add_meta_boxes_' . $post_type, $args->register_meta_box_cb, 10, 1 );
@@ -1709,6 +1718,40 @@ function is_post_type_viewable( $post_type ) {
 }
 
 /**
+ * Determines whether a post is considered "embeddable".
+ *
+ * The 'is_embeddable' value of the post's type will be evaluated.
+ *
+ * @since x.x.x
+ *
+ * @param int|WP_Post|null $post Optional. Post ID or `WP_Post` object. Defaults to global $post.
+ * @return bool Whether the post should be considered embeddable.
+ */
+function is_post_embeddable( $post = null ) {
+	$post = get_post( $post );
+
+	if ( ! $post ) {
+		return false;
+	}
+
+	$is_embeddable = false;
+
+	if ( $post_type = get_post_type_object( $post->post_type ) ) {
+		$is_embeddable = $post_type->is_embeddable;
+	}
+
+	/**
+	 * Filter whether a post is embeddable.
+	 *
+	 * @since x.x.x
+	 *
+	 * @param bool    $is_embeddable Whether the post is embeddable.
+	 * @param WP_Post $post          Post object.
+	 */
+	return apply_filters( 'is_post_embeddable', $is_embeddable, $post );
+}
+
+/**
  * Retrieve list of latest posts or posts matching criteria.
  *
  * The defaults are as follows:
diff --git tests/phpunit/tests/oembed/discovery.php tests/phpunit/tests/oembed/discovery.php
index d3fbab8..e43c2eb 100644
--- tests/phpunit/tests/oembed/discovery.php
+++ tests/phpunit/tests/oembed/discovery.php
@@ -71,4 +71,19 @@ class Tests_oEmbed_Discovery extends WP_UnitTestCase {
 
 		$this->assertEquals( $expected, get_echo( 'wp_oembed_add_discovery_links' ) );
 	}
+
+	/**
+	 * @ticket 35567
+	 */
+	function test_wp_oembed_add_discovery_links_non_embeddable_post_type_output_should_be_empty() {
+		register_post_type( 'not_embeddable', array( 'is_embeddable' => false ) );
+
+		$post = $this->factory()->post->create_and_get( array(
+			'post_type' => 'not_embeddable'
+		) );
+
+		$this->go_to( get_permalink( $post->ID ) );
+
+		$this->assertEmpty( wp_oembed_add_discovery_links() );
+	}
 }
diff --git tests/phpunit/tests/oembed/getResponseData.php tests/phpunit/tests/oembed/getResponseData.php
index 4a5c4f7..0ad9438 100644
--- tests/phpunit/tests/oembed/getResponseData.php
+++ tests/phpunit/tests/oembed/getResponseData.php
@@ -167,4 +167,17 @@ class Tests_oEmbed_Response_Data extends WP_UnitTestCase {
 		$this->assertArrayHasKey( 'thumbnail_height', $data );
 		$this->assertTrue( 400 >= $data['thumbnail_width'] );
 	}
+
+	/**
+	 * @ticket 35567
+	 */
+	function test_get_oembed_response_data_non_embeddable_post_type_should_return_false() {
+		register_post_type( 'not_emebeddable', array( 'is_embeddable' => false ) );
+
+		$post = self::factory()->post->create_and_get( array(
+			'post_type' => 'not_embeddable'
+		) );
+
+		$this->assertFalse( get_oembed_response_data( $post, 100 ) );
+	}
 }
diff --git tests/phpunit/tests/oembed/template.php tests/phpunit/tests/oembed/template.php
index 21fd485..c3b6116 100644
--- tests/phpunit/tests/oembed/template.php
+++ tests/phpunit/tests/oembed/template.php
@@ -301,4 +301,64 @@ class Tests_Embed_Template extends WP_UnitTestCase {
 		$this->assertNotContains( '&', file_get_contents( $file ) );
 	}
 
+	/**
+	 * @ticket 35567
+	 */
+	public function test_is_embeddable_post_non_existent_post() {
+		$this->assertFalse( is_post_embeddable( 0 ) );
+	}
+
+	/**
+	 * @ticket 35567
+	 */
+	public function test_is_embeddable_post_non_existent_post_type() {
+		$post = self::factory()->post->create_and_get( array(
+			'post_type' => rand_str(),
+		) );
+
+		$this->assertFalse( is_post_embeddable( $post ) );
+	}
+
+	/**
+	 * @ticket 35567
+	 */
+	public function test_is_embeddable_post_should_return_false_for_non_embeddable_post_type() {
+		register_post_type( 'not_emebeddable', array( 'is_embeddable' => false ) );
+
+		$post = self::factory()->post->create_and_get( array(
+			'post_type' => 'not_embeddable',
+		) );
+
+		$this->assertFalse( is_post_embeddable( $post ) );
+	}
+
+	/**
+	 * @ticket 35567
+	 */
+	public function test_is_embeddable_post_should_return_true_for_embeddable_post_type() {
+		register_post_type( 'not_emebeddable', array( 'is_embeddable' => true ) );
+
+		$post = self::factory()->post->create_and_get( array(
+			'post_type' => 'not_embeddable',
+		) );
+
+		$this->assertFalse( is_post_embeddable( $post ) );
+	}
+
+	/**
+	 * @ticket 35567
+	 */
+	public function test_is_embeddable_post_filtered() {
+		register_post_type( 'not_emebeddable', array( 'is_embeddable' => false ) );
+
+		$post = self::factory()->post->create_and_get( array(
+			'post_type' => 'not_embeddable',
+		) );
+
+		add_filter( 'is_post_embeddable', '__return_true' );
+		$is_embeddable = is_post_embeddable( $post );
+		remove_filter( 'is_post_embeddable', '__return_true' );
+
+		$this->assertTrue( $is_embeddable );
+	}
 }
diff --git tests/phpunit/tests/post/types.php tests/phpunit/tests/post/types.php
index 9cf44c8..da26008 100644
--- tests/phpunit/tests/post/types.php
+++ tests/phpunit/tests/post/types.php
@@ -428,4 +428,28 @@ class Tests_Post_Types extends WP_UnitTestCase {
 	public function test_get_post_types_by_support_non_existant_feature() {
 		$this->assertEqualSets( array(), get_post_types_by_support( 'somefeature' ) );
 	}
+
+	/**
+	 * @group oembed
+	 * @ticket 35567
+	 */
+	public function test_register_post_type_is_embeddable_defaults_to_public_argument() {
+		$post_type = register_post_type( rand_str( 10) );
+		$this->assertFalse( $post_type->is_embeddable );
+
+		$post_type = register_post_type( rand_str( 10 ), array( 'public' => true ) );
+		$this->assertTrue( $post_type->is_embeddable );
+	}
+
+	/**
+	 * @group oembed
+	 * @ticket 35567
+	 */
+	public function test_register_post_type_override_is_embeddable() {
+		$post_type = register_post_type( rand_str( 10 ), array( 'is_embeddable' => true ) );
+		$this->assertTrue( $post_type->is_embeddable );
+
+		$post_type = register_post_type( rand_str( 10 ), array( 'public' => true, 'is_embeddable' => false ) );
+		$this->assertFalse( $post_type->is_embeddable );
+	}
 }
