diff --git src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
index b7b2dd79f6..ab53cd8d9b 100644
--- src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
+++ src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
@@ -270,6 +270,7 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 		foreach ( $taxonomies as $taxonomy ) {
 			$base        = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 			$tax_exclude = $base . '_exclude';
+			$tax_and     = $base . '_and';
 
 			if ( ! empty( $request[ $base ] ) ) {
 				$query_args['tax_query'][] = array(
@@ -289,6 +290,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 					'operator'         => 'NOT IN',
 				);
 			}
+
+			if ( ! empty( $request[ $tax_and ] ) ) {
+				$query_args['tax_query'][] = array(
+					'taxonomy'         => $taxonomy->name,
+					'field'            => 'term_id',
+					'terms'            => $request[ $tax_and ],
+					'include_children' => false,
+					'operator'         => 'AND',
+				);
+			}
 		}
 
 		$posts_query  = new WP_Query();
@@ -2239,6 +2250,16 @@ class WP_REST_Posts_Controller extends WP_REST_Controller {
 				),
 				'default'     => array(),
 			);
+
+			$query_params[ $base . '_and' ] = array(
+				/* translators: %s: taxonomy name */
+				'description' => sprintf( __( 'Limit result set to all items that have all the specified terms assigned in the %s taxonomy.' ), $base ),
+				'type'        => 'array',
+				'items'       => array(
+					'type' => 'integer',
+				),
+				'default'     => array(),
+			);
 		}
 
 		if ( 'post' === $this->post_type ) {
diff --git tests/phpunit/tests/rest-api/rest-posts-controller.php tests/phpunit/tests/rest-api/rest-posts-controller.php
index 721b08292a..64e085a497 100644
--- tests/phpunit/tests/rest-api/rest-posts-controller.php
+++ tests/phpunit/tests/rest-api/rest-posts-controller.php
@@ -146,6 +146,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 				'author_exclude',
 				'before',
 				'categories',
+				'categories_and',
 				'categories_exclude',
 				'context',
 				'exclude',
@@ -160,6 +161,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 				'status',
 				'sticky',
 				'tags',
+				'tags_and',
 				'tags_exclude',
 			), $keys
 		);
@@ -865,6 +867,25 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 		$this->assertEquals( $id1, $data[0]['id'] );
 	}
 
+	public function test_get_items_tags_and_query() {
+		$id1 = self::$post_id;
+		$id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$id3 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$tag1 = wp_insert_term( 'Tag1', 'post_tag' );
+		$tag2 = wp_insert_term( 'Tag2', 'post_tag' );
+
+		wp_set_object_terms( $id1, array( $tag1['term_id'] ), 'post_tag' );
+		wp_set_object_terms( $id2, array( $tag2['term_id'] ), 'post_tag' );
+		wp_set_object_terms( $id3, array( $tag1['term_id'], $tag2['term_id'] ), 'post_tag' );
+		$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
+		$request->set_param( 'tags_and', array( $tag1['term_id'], $tag2['term_id'] ) );
+
+		$response = rest_get_server()->dispatch( $request );
+		$data     = $response->get_data();
+		$this->assertCount( 1, $data );
+		$this->assertEquals( $id3, $data[0]['id'] );
+	}
+
 	public function test_get_items_tags_exclude_query() {
 		$id1 = self::$post_id;
 		$id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
@@ -934,6 +955,31 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 		$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
 	}
 
+	public function test_get_items_tags_and_and_categories_query() {
+		$id1      = self::$post_id;
+		$id2      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$id3      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$id4      = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+		$tag1     = wp_insert_term( 'Tag1', 'post_tag' );
+		$tag2     = wp_insert_term( 'Tag2', 'post_tag' );
+		$category = wp_insert_term( 'My Category', 'category' );
+
+		wp_set_object_terms( $id1, array( $tag1['term_id'], $tag2['term_id'] ), 'post_tag' );
+		wp_set_object_terms( $id1, array( $category['term_id'] ), 'category' );
+		wp_set_object_terms( $id2, array( $tag1['term_id'] ), 'post_tag' );
+		wp_set_object_terms( $id2, array( $category['term_id'] ), 'category' );
+		wp_set_object_terms( $id3, array( $category['term_id'] ), 'category' );
+
+		$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
+		$request->set_param( 'tags_and', array( $tag1['term_id'], $tag2['term_id'] ) );
+		$request->set_param( 'categories', array( $category['term_id'] ) );
+
+		$response = rest_get_server()->dispatch( $request );
+		$data     = $response->get_data();
+		$this->assertCount( 1, $data );
+		$this->assertEquals( $id1, $data[0]['id'] );
+	}
+
 	public function test_get_items_sticky() {
 		$id1 = self::$post_id;
 		$id2 = $this->factory->post->create( array( 'post_status' => 'publish' ) );
diff --git tests/qunit/fixtures/wp-api-generated.js tests/qunit/fixtures/wp-api-generated.js
index fd20726c9d..49f1d6b930 100644
--- tests/qunit/fixtures/wp-api-generated.js
+++ tests/qunit/fixtures/wp-api-generated.js
@@ -340,6 +340,15 @@ mockedApiResponse.Schema = {
                                 "type": "integer"
                             }
                         },
+                        "categories_and": {
+                            "required": false,
+                            "default": [],
+                            "description": "Limit result set to all items that have all the specified terms assigned in the categories taxonomy.",
+                            "type": "array",
+                            "items": {
+                                "type": "integer"
+                            }
+                        },
                         "tags": {
                             "required": false,
                             "default": [],
@@ -358,6 +367,15 @@ mockedApiResponse.Schema = {
                                 "type": "integer"
                             }
                         },
+                        "tags_and": {
+                            "required": false,
+                            "default": [],
+                            "description": "Limit result set to all items that have all the specified terms assigned in the tags taxonomy.",
+                            "type": "array",
+                            "items": {
+                                "type": "integer"
+                            }
+                        },
                         "sticky": {
                             "required": false,
                             "description": "Limit result set to items that are sticky.",
