diff --git a/services/blob/src/http/handlers/holders.rs b/services/blob/src/http/handlers/holders.rs
--- a/services/blob/src/http/handlers/holders.rs
+++ b/services/blob/src/http/handlers/holders.rs
@@ -1,4 +1,4 @@
-use actix_web::error::ErrorBadRequest;
+use actix_web::error::{ErrorBadRequest, ErrorNotImplemented};
 use actix_web::{web, HttpResponse};
 use comm_lib::blob::types::http::{
   AssignHoldersRequest, AssignHoldersResponse, BlobInfo,
@@ -63,10 +63,13 @@
   service: web::Data<BlobService>,
   payload: web::Json<RemoveHoldersRequest>,
 ) -> actix_web::Result<HttpResponse> {
-  let RemoveHoldersRequest {
+  let RemoveHoldersRequest::Items {
     requests,
     instant_delete,
-  } = payload.into_inner();
+  } = payload.into_inner()
+  else {
+    return Err(ErrorNotImplemented("not implemented"));
+  };
   info!(
     instant_delete,
     "Remove request for {} holders.",
diff --git a/shared/comm-lib/src/blob/client.rs b/shared/comm-lib/src/blob/client.rs
--- a/shared/comm-lib/src/blob/client.rs
+++ b/shared/comm-lib/src/blob/client.rs
@@ -253,7 +253,7 @@
     debug!(num_holders, "Revoke multiple holders request.");
 
     let url = self.get_holders_url()?;
-    let payload = RemoveHoldersRequest {
+    let payload = RemoveHoldersRequest::Items {
       requests: blob_infos,
       instant_delete: false,
     };
diff --git a/shared/comm-lib/src/blob/types.rs b/shared/comm-lib/src/blob/types.rs
--- a/shared/comm-lib/src/blob/types.rs
+++ b/shared/comm-lib/src/blob/types.rs
@@ -1,5 +1,6 @@
 use derive_more::Constructor;
 use hex::ToHex;
+use serde::{Deserialize, Serialize};
 use sha2::{Digest, Sha256};
 
 /// This module defines structures for HTTP requests and responses
@@ -38,12 +39,23 @@
 
   // Remove multiple holders
   #[derive(Serialize, Deserialize, Debug)]
-  #[serde(rename_all = "camelCase")]
-  pub struct RemoveHoldersRequest {
-    pub requests: Vec<BlobInfo>,
-    #[serde(default)]
-    pub instant_delete: bool,
+  #[serde(untagged)]
+  pub enum RemoveHoldersRequest {
+    // remove holders with given (hash, holder) pairs
+    #[serde(rename_all = "camelCase")]
+    Items {
+      requests: Vec<BlobInfo>,
+      /// If true, the blobs will be deleted instantly
+      /// after their last holders are revoked.
+      #[serde(default)]
+      instant_delete: bool,
+    },
+    // remove all holders that are indexed by any of given tags
+    ByIndexedTags {
+      tags: Vec<String>,
+    },
   }
+
   #[derive(Serialize, Deserialize, Debug)]
   #[serde(rename_all = "camelCase")]
   pub struct RemoveHoldersResponse {
@@ -66,7 +78,7 @@
   pub struct RemoveHolderRequest {
     pub blob_hash: String,
     pub holder: String,
-    /// If true, the blob will be deleted intantly
+    /// If true, the blob will be deleted instantly
     /// after the last holder is revoked.
     #[serde(default)]
     pub instant_delete: bool,
@@ -74,7 +86,7 @@
 }
 
 /// Blob owning information - stores both blob_hash and holder
-#[derive(Clone, Debug, Constructor, serde::Serialize, serde::Deserialize)]
+#[derive(Clone, Debug, Constructor, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 pub struct BlobInfo {
   pub blob_hash: String,
@@ -139,3 +151,67 @@
     }
   }
 }
+
+#[cfg(test)]
+mod serialization_tests {
+  use super::http::*;
+  mod remove_holders_request {
+    use super::*;
+
+    #[test]
+    fn serialize_items() {
+      let req = RemoveHoldersRequest::Items {
+        requests: vec![BlobInfo::new("a".into(), "b".into())],
+        instant_delete: false,
+      };
+      let expected =
+        r#"{"requests":[{"blobHash":"a","holder":"b"}],"instantDelete":false}"#;
+      assert_eq!(expected, serde_json::to_string(&req).unwrap());
+    }
+
+    #[test]
+    fn deserialize_items() {
+      let json =
+        r#"{"requests":[{"blobHash":"a","holder":"b"}],"instantDelete":false}"#;
+      let deserialized: RemoveHoldersRequest =
+        serde_json::from_str(json).expect("Request JSON payload invalid");
+
+      let expected_items = vec![BlobInfo::new("a".into(), "b".into())];
+
+      let is_matching = matches!(
+        deserialized,
+        RemoveHoldersRequest::Items {
+          requests: items,
+          instant_delete: false,
+        } if items == expected_items
+      );
+      assert!(is_matching, "Deserialized request is incorrect");
+    }
+
+    #[test]
+    fn serialize_tags() {
+      let req = RemoveHoldersRequest::ByIndexedTags {
+        tags: vec!["foo".into(), "bar".into()],
+      };
+      let expected = r#"{"tags":["foo","bar"]}"#;
+      assert_eq!(expected, serde_json::to_string(&req).unwrap());
+    }
+
+    #[test]
+    fn deserialize_tags() {
+      let json = r#"{"tags":["foo","bar"]}"#;
+      let deserialized: RemoveHoldersRequest =
+        serde_json::from_str(json).expect("Request JSON payload invalid");
+
+      let expected_tags: Vec<String> = vec!["foo".into(), "bar".into()];
+
+      let is_matching = matches!(
+        deserialized,
+        RemoveHoldersRequest::ByIndexedTags {
+          tags: actual_tags
+        } if actual_tags == expected_tags
+      );
+      assert!(is_matching, "Deserialized request is incorrect");
+    }
+  }
+}