You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ge...@apache.org on 2023/08/24 11:59:16 UTC

[solr] branch main updated: SOLR-16825: Migrate v2 definitions to 'api' module (#1859)

This is an automated email from the ASF dual-hosted git repository.

gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 0a47cf8ac84 SOLR-16825: Migrate v2 definitions to 'api' module (#1859)
0a47cf8ac84 is described below

commit 0a47cf8ac8420feb623600ff736aed6503b53b20
Author: Jason Gerlowski <ge...@apache.org>
AuthorDate: Thu Aug 24 07:59:08 2023 -0400

    SOLR-16825: Migrate v2 definitions to 'api' module (#1859)
    
    Since Solr's OAS (OpenApi Spec) is now generated prior to compiling
    SolrJ, it can only contain those APIs defined in our new 'api'
    module.
    
    This commit moves a number of v2 API definitions to this new module,
    including:
      - delete replica property
      - delete replica
      - delete (i.e. "clear") node
      - delete collection snapshot
      - delete collection backup
    
    Moving these APIs also means that a v2 SolrRequest implementation is
    generated for each.
---
 .../client/api/endpoint/AddReplicaPropertyApi.java |   2 +-
 .../api/endpoint/DeleteCollectionBackupApi.java    |  83 ++++++++++++++++
 .../api/endpoint/DeleteCollectionSnapshotApi.java  |  50 ++++++++++
 ...dReplicaPropertyApi.java => DeleteNodeApi.java} |  38 +++-----
 .../solr/client/api/endpoint/DeleteReplicaApi.java |  94 ++++++++++++++++++
 ...pertyApi.java => DeleteReplicaPropertyApi.java} |  27 +++---
 .../solr/client/api/model/BackupDeletionData.java  |  27 ++++++
 .../api/model/BackupDeletionResponseBody.java      |  26 +++++
 .../apache/solr/client/api/model/Constants.java    |  51 ++++++++++
 .../model/DeleteCollectionSnapshotResponse.java    |  42 ++++++++
 .../client/api/model/DeleteNodeRequestBody.java    |  27 ++++++
 .../api/model/PurgeUnusedFilesRequestBody.java     |  42 ++++++++
 .../solr/client/api/model/PurgeUnusedResponse.java |  30 ++++++
 .../api/model/ScaleCollectionRequestBody.java      |  41 ++++++++
 .../solr/handler/admin/CollectionsHandler.java     |  38 ++++----
 .../admin/api/CreateCollectionBackupAPI.java       |   8 +-
 ...nBackupAPI.java => DeleteCollectionBackup.java} | 103 ++++++--------------
 ...pshotAPI.java => DeleteCollectionSnapshot.java} |  59 ++----------
 .../api/{DeleteNodeAPI.java => DeleteNode.java}    |  48 ++-------
 .../{DeleteReplicaAPI.java => DeleteReplica.java}  | 107 +++++++++------------
 ...PropertyAPI.java => DeleteReplicaProperty.java} |  35 +++----
 .../admin/api/DeleteCollectionBackupAPITest.java   |  21 ++--
 .../admin/api/DeleteCollectionSnapshotAPITest.java |   5 +-
 .../solr/handler/admin/api/DeleteNodeAPITest.java  |  15 +--
 .../handler/admin/api/DeleteReplicaAPITest.java    |  12 +--
 .../admin/api/DeleteReplicaPropertyAPITest.java    |  22 ++---
 .../solrj/src/resources/java-template/api.mustache |  16 +--
 27 files changed, 708 insertions(+), 361 deletions(-)

diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
index d29ee057502..9403358770a 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
@@ -36,7 +36,7 @@ public interface AddReplicaPropertyApi {
   @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
   @Operation(
       summary = "Adds a property to the specified replica",
-      tags = {"replicas"})
+      tags = {"replica-properties"})
   public SolrJerseyResponse addReplicaProperty(
       @Parameter(
               description = "The name of the collection the replica belongs to.",
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionBackupApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionBackupApi.java
new file mode 100644
index 00000000000..1135e4d89d7
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionBackupApi.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.endpoint;
+
+import static org.apache.solr.client.api.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.BACKUP_LOCATION;
+import static org.apache.solr.client.api.model.Constants.BACKUP_REPOSITORY;
+import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.BackupDeletionResponseBody;
+import org.apache.solr.client.api.model.PurgeUnusedFilesRequestBody;
+import org.apache.solr.client.api.model.PurgeUnusedResponse;
+
+@Path("/backups/{backupName}")
+public interface DeleteCollectionBackupApi {
+
+  @Path("/versions/{backupId}")
+  @DELETE
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Delete incremental backup point by ID",
+      tags = {"collection-backups"})
+  BackupDeletionResponseBody deleteSingleBackupById(
+      @PathParam("backupName") String backupName,
+      @PathParam("backupId") String backupId,
+      // Optional parameters below
+      @QueryParam(BACKUP_LOCATION) String location,
+      @QueryParam(BACKUP_REPOSITORY) String repositoryName,
+      @QueryParam(ASYNC) String asyncId)
+      throws Exception;
+
+  @Path("/versions")
+  @DELETE
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Delete all incremental backup points older than the most recent N",
+      tags = {"collection-backups"})
+  BackupDeletionResponseBody deleteMultipleBackupsByRecency(
+      @PathParam("backupName") String backupName,
+      @QueryParam("retainLatest") Integer versionsToRetain,
+      // Optional parameters below
+      @QueryParam(BACKUP_LOCATION) String location,
+      @QueryParam(BACKUP_REPOSITORY) String repositoryName,
+      @QueryParam(ASYNC) String asyncId)
+      throws Exception;
+
+  @Path("/purgeUnused")
+  @PUT
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Garbage collect orphaned incremental backup files",
+      tags = {"collection-backups"})
+  PurgeUnusedResponse garbageCollectUnusedBackupFiles(
+      @PathParam("backupName") String backupName,
+      @RequestBody(
+              description = "Request body parameters for the orphaned file cleanup",
+              required = false)
+          PurgeUnusedFilesRequestBody requestBody)
+      throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
new file mode 100644
index 00000000000..f9d1afc8387
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteCollectionSnapshotApi.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.endpoint;
+
+import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
+
+public interface DeleteCollectionSnapshotApi {
+
+  /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT) */
+  @DELETE
+  @Path("/collections/{collName}/snapshots/{snapshotName}")
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  DeleteCollectionSnapshotResponse deleteSnapshot(
+      @Parameter(description = "The name of the collection.", required = true)
+          @PathParam("collName")
+          String collName,
+      @Parameter(description = "The name of the snapshot to be deleted.", required = true)
+          @PathParam("snapshotName")
+          String snapshotName,
+      @Parameter(description = "A flag that treats the collName parameter as a collection alias.")
+          @DefaultValue("false")
+          @QueryParam("followAliases")
+          boolean followAliases,
+      @QueryParam("async") String asyncId)
+      throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteNodeApi.java
similarity index 54%
copy from solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
copy to solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteNodeApi.java
index d29ee057502..323b70dbabb 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteNodeApi.java
@@ -22,39 +22,29 @@ import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.parameters.RequestBody;
-import javax.ws.rs.PUT;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
-import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
+import org.apache.solr.client.api.model.DeleteNodeRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 
-@Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
-public interface AddReplicaPropertyApi {
+@Path("cluster/nodes/{nodeName}/clear/")
+public interface DeleteNodeApi {
 
-  @PUT
+  @POST
   @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
   @Operation(
-      summary = "Adds a property to the specified replica",
-      tags = {"replicas"})
-  public SolrJerseyResponse addReplicaProperty(
+      summary = "Delete all replicas off of the specified SolrCloud node",
+      tags = {"node"})
+  SolrJerseyResponse deleteNode(
       @Parameter(
-              description = "The name of the collection the replica belongs to.",
+              description =
+                  "The name of the node to be cleared.  Usually of the form 'host:1234_solr'.",
               required = true)
-          @PathParam("collName")
-          String collName,
-      @Parameter(description = "The name of the shard the replica belongs to.", required = true)
-          @PathParam("shardName")
-          String shardName,
-      @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
-          @PathParam("replicaName")
-          String replicaName,
-      @Parameter(description = "The name of the property to add.", required = true)
-          @PathParam("propName")
-          String propertyName,
-      @RequestBody(
-              description = "The value of the replica property to create or update",
-              required = true)
-          AddReplicaPropertyRequestBody requestBody)
+          @PathParam("nodeName")
+          String nodeName,
+      @RequestBody(description = "Contains user provided parameters", required = true)
+          DeleteNodeRequestBody requestBody)
       throws Exception;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaApi.java
new file mode 100644
index 00000000000..a728c12aadc
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaApi.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.endpoint;
+
+import static org.apache.solr.client.api.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.COUNT_PROP;
+import static org.apache.solr.client.api.model.Constants.DELETE_DATA_DIR;
+import static org.apache.solr.client.api.model.Constants.DELETE_INDEX;
+import static org.apache.solr.client.api.model.Constants.DELETE_INSTANCE_DIR;
+import static org.apache.solr.client.api.model.Constants.FOLLOW_ALIASES;
+import static org.apache.solr.client.api.model.Constants.ONLY_IF_DOWN;
+import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
+
+import io.swagger.v3.oas.annotations.Operation;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.model.ScaleCollectionRequestBody;
+import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
+
+/**
+ * V2 API definition for deleting one or more existing replicas from one or more shards.
+ *
+ * <p>These APIs are analogous to the v1 /admin/collections?action=DELETEREPLICA command.
+ */
+@Path("/collections/{collectionName}")
+public interface DeleteReplicaApi {
+
+  @DELETE
+  @Path("/shards/{shardName}/replicas/{replicaName}")
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Delete an single replica by name",
+      tags = {"replicas"})
+  SubResponseAccumulatingJerseyResponse deleteReplicaByName(
+      @PathParam("collectionName") String collectionName,
+      @PathParam("shardName") String shardName,
+      @PathParam("replicaName") String replicaName,
+      // Optional params below
+      @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
+      @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
+      @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
+      @QueryParam(DELETE_INDEX) Boolean deleteIndex,
+      @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
+      @QueryParam(ASYNC) String async)
+      throws Exception;
+
+  @DELETE
+  @Path("/shards/{shardName}/replicas")
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Delete one or more replicas from the specified collection and shard",
+      tags = {"replicas"})
+  SubResponseAccumulatingJerseyResponse deleteReplicasByCount(
+      @PathParam("collectionName") String collectionName,
+      @PathParam("shardName") String shardName,
+      @QueryParam(COUNT_PROP) Integer numToDelete,
+      // Optional params below
+      @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
+      @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
+      @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
+      @QueryParam(DELETE_INDEX) Boolean deleteIndex,
+      @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
+      @QueryParam(ASYNC) String async)
+      throws Exception;
+
+  @PUT
+  @Path("/scale")
+  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Operation(
+      summary = "Scale the replica count for all shards in the specified collection",
+      tags = {"replicas"})
+  SubResponseAccumulatingJerseyResponse deleteReplicasByCountAllShards(
+      @PathParam("collectionName") String collectionName, ScaleCollectionRequestBody requestBody)
+      throws Exception;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaPropertyApi.java
similarity index 74%
copy from solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
copy to solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaPropertyApi.java
index d29ee057502..15d98564ffe 100644
--- a/solr/api/src/java/org/apache/solr/client/api/endpoint/AddReplicaPropertyApi.java
+++ b/solr/api/src/java/org/apache/solr/client/api/endpoint/DeleteReplicaPropertyApi.java
@@ -21,23 +21,26 @@ import static org.apache.solr.client.api.util.Constants.BINARY_CONTENT_TYPE_V2;
 
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
-import javax.ws.rs.PUT;
+import javax.ws.rs.DELETE;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
-import org.apache.solr.client.api.model.AddReplicaPropertyRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 
+/**
+ * V2 API definition for removing a property from a collection replica
+ *
+ * <p>This API is analogous to the v1 /admin/collections?action=DELETEREPLICAPROP command.
+ */
 @Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
-public interface AddReplicaPropertyApi {
+public interface DeleteReplicaPropertyApi {
 
-  @PUT
+  @DELETE
   @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
   @Operation(
-      summary = "Adds a property to the specified replica",
-      tags = {"replicas"})
-  public SolrJerseyResponse addReplicaProperty(
+      summary = "Delete an existing replica property",
+      tags = {"replica-properties"})
+  SolrJerseyResponse deleteReplicaProperty(
       @Parameter(
               description = "The name of the collection the replica belongs to.",
               required = true)
@@ -49,12 +52,8 @@ public interface AddReplicaPropertyApi {
       @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
           @PathParam("replicaName")
           String replicaName,
-      @Parameter(description = "The name of the property to add.", required = true)
+      @Parameter(description = "The name of the property to delete.", required = true)
           @PathParam("propName")
-          String propertyName,
-      @RequestBody(
-              description = "The value of the replica property to create or update",
-              required = true)
-          AddReplicaPropertyRequestBody requestBody)
+          String propertyName)
       throws Exception;
 }
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionData.java b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionData.java
new file mode 100644
index 00000000000..b27ce790c89
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionData.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackupDeletionData {
+  @JsonProperty public String startTime;
+  @JsonProperty public Integer backupId;
+  @JsonProperty public Long size;
+  @JsonProperty public Integer numFiles;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionResponseBody.java b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionResponseBody.java
new file mode 100644
index 00000000000..29563599ab2
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/BackupDeletionResponseBody.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+
+public class BackupDeletionResponseBody extends SubResponseAccumulatingJerseyResponse {
+  @JsonProperty public String collection;
+  @JsonProperty public List<BackupDeletionData> deleted;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/Constants.java b/solr/api/src/java/org/apache/solr/client/api/model/Constants.java
new file mode 100644
index 00000000000..e6ad827756f
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/Constants.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+public class Constants {
+
+  private Constants() {
+    /* Private ctor prevents instantiation */
+  }
+
+  /** A parameter to specify the name of the backup repository to be used. */
+  public static final String BACKUP_REPOSITORY = "repository";
+
+  /** A parameter to specify the location where the backup should be stored. */
+  public static final String BACKUP_LOCATION = "location";
+
+  /** Async or not? * */
+  public static final String ASYNC = "async";
+
+  /** The name of a collection referenced by an API call */
+  public static final String COLLECTION = "collection";
+
+  public static final String COUNT_PROP = "count";
+
+  /** Option to follow aliases when deciding the target of a collection admin command. */
+  public static final String FOLLOW_ALIASES = "followAliases";
+
+  /** If you unload a core, delete the index too */
+  public static final String DELETE_INDEX = "deleteIndex";
+
+  public static final String DELETE_DATA_DIR = "deleteDataDir";
+
+  public static final String DELETE_INSTANCE_DIR = "deleteInstanceDir";
+
+  public static final String ONLY_IF_DOWN = "onlyIfDown";
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
new file mode 100644
index 00000000000..2b134a2877c
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/DeleteCollectionSnapshotResponse.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import static org.apache.solr.client.api.model.Constants.COLLECTION;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
+
+/**
+ * The Response for {@link DeleteCollectionSnapshotApi#deleteSnapshot(String, String, boolean,
+ * String)}
+ */
+public class DeleteCollectionSnapshotResponse extends AsyncJerseyResponse {
+  @Schema(description = "The name of the collection.")
+  @JsonProperty(COLLECTION)
+  public String collection;
+
+  @Schema(description = "The name of the snapshot to be deleted.")
+  @JsonProperty("snapshot")
+  public String snapshotName;
+
+  @Schema(description = "A flag that treats the collName parameter as a collection alias.")
+  @JsonProperty("followAliases")
+  public boolean followAliases;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/DeleteNodeRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/DeleteNodeRequestBody.java
new file mode 100644
index 00000000000..4b05f2f04e9
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/DeleteNodeRequestBody.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+public class DeleteNodeRequestBody {
+  @Schema(description = "Request ID to track this action which will be processed asynchronously.")
+  @JsonProperty("async")
+  public String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedFilesRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedFilesRequestBody.java
new file mode 100644
index 00000000000..1e3c6a26324
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedFilesRequestBody.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import static org.apache.solr.client.api.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.BACKUP_LOCATION;
+import static org.apache.solr.client.api.model.Constants.BACKUP_REPOSITORY;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.solr.client.api.endpoint.DeleteCollectionBackupApi;
+
+/**
+ * Request body for the {@link DeleteCollectionBackupApi#garbageCollectUnusedBackupFiles(String,
+ * PurgeUnusedFilesRequestBody)} API.
+ */
+public class PurgeUnusedFilesRequestBody {
+  @JsonProperty(BACKUP_LOCATION)
+  public String location;
+
+  @Schema(name = "repositoryName")
+  @JsonProperty(BACKUP_REPOSITORY)
+  public String repositoryName;
+
+  @JsonProperty(ASYNC)
+  public String async;
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedResponse.java b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedResponse.java
new file mode 100644
index 00000000000..5415060149c
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/PurgeUnusedResponse.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class PurgeUnusedResponse extends SubResponseAccumulatingJerseyResponse {
+  @JsonProperty public PurgeUnusedStats deleted;
+
+  public static class PurgeUnusedStats {
+    @JsonProperty public Integer numBackupIds;
+    @JsonProperty public Integer numShardBackupIds;
+    @JsonProperty public Integer numIndexFiles;
+  }
+}
diff --git a/solr/api/src/java/org/apache/solr/client/api/model/ScaleCollectionRequestBody.java b/solr/api/src/java/org/apache/solr/client/api/model/ScaleCollectionRequestBody.java
new file mode 100644
index 00000000000..c1c84ca22e1
--- /dev/null
+++ b/solr/api/src/java/org/apache/solr/client/api/model/ScaleCollectionRequestBody.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.client.api.model;
+
+import static org.apache.solr.client.api.model.Constants.ASYNC;
+import static org.apache.solr.client.api.model.Constants.COUNT_PROP;
+import static org.apache.solr.client.api.model.Constants.DELETE_DATA_DIR;
+import static org.apache.solr.client.api.model.Constants.DELETE_INDEX;
+import static org.apache.solr.client.api.model.Constants.DELETE_INSTANCE_DIR;
+import static org.apache.solr.client.api.model.Constants.FOLLOW_ALIASES;
+import static org.apache.solr.client.api.model.Constants.ONLY_IF_DOWN;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+
+/** Request body used by {@link org.apache.solr.client.api.endpoint.DeleteReplicaApi} */
+public class ScaleCollectionRequestBody {
+  public @JsonProperty(value = COUNT_PROP, required = true) @Schema(name = "numToDelete") Integer
+      numToDelete;
+  public @JsonProperty(FOLLOW_ALIASES) Boolean followAliases;
+  public @JsonProperty(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir;
+  public @JsonProperty(DELETE_DATA_DIR) Boolean deleteDataDir;
+  public @JsonProperty(DELETE_INDEX) Boolean deleteIndex;
+  public @JsonProperty(ONLY_IF_DOWN) Boolean onlyIfDown;
+  public @JsonProperty(ASYNC) String async;
+}
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
index 4b63e0c5138..732d7eb03c2 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java
@@ -177,11 +177,11 @@ import org.apache.solr.handler.admin.api.CreateReplicaAPI;
 import org.apache.solr.handler.admin.api.CreateShardAPI;
 import org.apache.solr.handler.admin.api.DeleteAlias;
 import org.apache.solr.handler.admin.api.DeleteCollection;
-import org.apache.solr.handler.admin.api.DeleteCollectionBackupAPI;
-import org.apache.solr.handler.admin.api.DeleteCollectionSnapshotAPI;
-import org.apache.solr.handler.admin.api.DeleteNodeAPI;
-import org.apache.solr.handler.admin.api.DeleteReplicaAPI;
-import org.apache.solr.handler.admin.api.DeleteReplicaPropertyAPI;
+import org.apache.solr.handler.admin.api.DeleteCollectionBackup;
+import org.apache.solr.handler.admin.api.DeleteCollectionSnapshot;
+import org.apache.solr.handler.admin.api.DeleteNode;
+import org.apache.solr.handler.admin.api.DeleteReplica;
+import org.apache.solr.handler.admin.api.DeleteReplicaProperty;
 import org.apache.solr.handler.admin.api.DeleteShardAPI;
 import org.apache.solr.handler.admin.api.ForceLeaderAPI;
 import org.apache.solr.handler.admin.api.InstallShardDataAPI;
@@ -734,7 +734,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     DELETEREPLICA_OP(
         DELETEREPLICA,
         (req, rsp, h) -> {
-          DeleteReplicaAPI.invokeWithV1Params(h.coreContainer, req, rsp);
+          DeleteReplica.invokeWithV1Params(h.coreContainer, req, rsp);
           return null;
         }),
     MIGRATE_OP(
@@ -1013,9 +1013,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     DELETEREPLICAPROP_OP(
         DELETEREPLICAPROP,
         (req, rsp, h) -> {
-          final var api = new DeleteReplicaPropertyAPI(h.coreContainer, req, rsp);
+          final var api = new DeleteReplicaProperty(h.coreContainer, req, rsp);
           final var deleteReplicaPropResponse =
-              DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, req.getParams());
+              DeleteReplicaProperty.invokeUsingV1Inputs(api, req.getParams());
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteReplicaPropResponse);
           return null;
         }),
@@ -1096,7 +1096,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     DELETEBACKUP_OP(
         DELETEBACKUP,
         (req, rsp, h) -> {
-          DeleteCollectionBackupAPI.invokeFromV1Params(h.coreContainer, req, rsp);
+          DeleteCollectionBackup.invokeFromV1Params(h.coreContainer, req, rsp);
           return null;
         }),
     LISTBACKUP_OP(
@@ -1142,10 +1142,10 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
           final boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false);
           final String asyncId = req.getParams().get(ASYNC);
 
-          final DeleteCollectionSnapshotAPI deleteCollectionSnapshotAPI =
-              new DeleteCollectionSnapshotAPI(h.coreContainer, req, rsp);
+          final DeleteCollectionSnapshot deleteCollectionSnapshotAPI =
+              new DeleteCollectionSnapshot(h.coreContainer, req, rsp);
 
-          final DeleteCollectionSnapshotAPI.DeleteSnapshotResponse deleteSnapshotResponse =
+          final var deleteSnapshotResponse =
               deleteCollectionSnapshotAPI.deleteSnapshot(
                   extCollectionName, commitName, followAliases, asyncId);
 
@@ -1207,9 +1207,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
     DELETENODE_OP(
         DELETENODE,
         (req, rsp, h) -> {
-          final DeleteNodeAPI deleteNodeAPI = new DeleteNodeAPI(h.coreContainer, req, rsp);
+          final DeleteNode deleteNodeAPI = new DeleteNode(h.coreContainer, req, rsp);
           final SolrJerseyResponse deleteNodeResponse =
-              DeleteNodeAPI.invokeUsingV1Inputs(deleteNodeAPI, req.getParams());
+              DeleteNode.invokeUsingV1Inputs(deleteNodeAPI, req.getParams());
           V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, deleteNodeResponse);
           return null;
         }),
@@ -1371,10 +1371,10 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
         CreateCollectionBackupAPI.class,
         CreateShardAPI.class,
         DeleteAlias.class,
-        DeleteCollectionBackupAPI.class,
+        DeleteCollectionBackup.class,
         DeleteCollection.class,
-        DeleteReplicaAPI.class,
-        DeleteReplicaPropertyAPI.class,
+        DeleteReplica.class,
+        DeleteReplicaProperty.class,
         DeleteShardAPI.class,
         ForceLeaderAPI.class,
         InstallShardDataAPI.class,
@@ -1388,12 +1388,12 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
         RestoreCollectionAPI.class,
         SyncShardAPI.class,
         CollectionPropertyAPI.class,
-        DeleteNodeAPI.class,
+        DeleteNode.class,
         ListAliasesAPI.class,
         AliasPropertyAPI.class,
         ListCollectionSnapshotsAPI.class,
         CreateCollectionSnapshotAPI.class,
-        DeleteCollectionSnapshotAPI.class);
+        DeleteCollectionSnapshot.class);
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
index 5dd9fd96e09..0339b29a887 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionBackupAPI.java
@@ -43,6 +43,7 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
+import org.apache.solr.client.api.model.BackupDeletionData;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
@@ -220,11 +221,4 @@ public class CreateCollectionBackupAPI extends BackupAPIBase {
 
     @JsonProperty public List<String> shardBackupIds;
   }
-
-  public static class BackupDeletionData implements JacksonReflectMapWriter {
-    @JsonProperty public String startTime;
-    @JsonProperty public Integer backupId;
-    @JsonProperty public Long size;
-    @JsonProperty public Integer numFiles;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackup.java
similarity index 73%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackup.java
index 48a482dce3e..88afdc1eb50 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionBackup.java
@@ -17,7 +17,6 @@
 
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -30,7 +29,6 @@ import static org.apache.solr.common.params.CoreAdminParams.MAX_NUM_BACKUP_POINT
 import static org.apache.solr.common.params.CoreAdminParams.NAME;
 import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -38,14 +36,12 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.endpoint.DeleteCollectionBackupApi;
+import org.apache.solr.client.api.model.BackupDeletionData;
+import org.apache.solr.client.api.model.BackupDeletionResponseBody;
+import org.apache.solr.client.api.model.PurgeUnusedFilesRequestBody;
+import org.apache.solr.client.api.model.PurgeUnusedResponse;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
-import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -54,7 +50,6 @@ import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.SimpleOrderedMap;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.jersey.SolrJacksonMapper;
 import org.apache.solr.request.SolrQueryRequest;
@@ -65,12 +60,12 @@ import org.apache.solr.response.SolrQueryResponse;
  *
  * <p>These APIs are equivalent to the v1 '/admin/collections?action=DELETEBACKUP' command.
  */
-public class DeleteCollectionBackupAPI extends BackupAPIBase {
+public class DeleteCollectionBackup extends BackupAPIBase implements DeleteCollectionBackupApi {
 
   private final ObjectMapper objectMapper;
 
   @Inject
-  public DeleteCollectionBackupAPI(
+  public DeleteCollectionBackup(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
@@ -79,17 +74,10 @@ public class DeleteCollectionBackupAPI extends BackupAPIBase {
     this.objectMapper = SolrJacksonMapper.getObjectMapper();
   }
 
-  @Path("/backups/{backupName}/versions/{backupId}")
-  @DELETE
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public BackupDeletionResponseBody deleteSingleBackupById(
-      @PathParam("backupName") String backupName,
-      @PathParam(BACKUP_ID) String backupId,
-      // Optional parameters below
-      @QueryParam(BACKUP_LOCATION) String location,
-      @QueryParam(BACKUP_REPOSITORY) String repositoryName,
-      @QueryParam(ASYNC) String asyncId)
+      String backupName, String backupId, String location, String repositoryName, String asyncId)
       throws Exception {
     final var response = instantiateJerseyResponse(BackupDeletionResponseBody.class);
     recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -108,17 +96,14 @@ public class DeleteCollectionBackupAPI extends BackupAPIBase {
     return response;
   }
 
-  @Path("/backups/{backupName}/versions")
-  @DELETE
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public BackupDeletionResponseBody deleteMultipleBackupsByRecency(
-      @PathParam("backupName") String backupName,
-      @QueryParam("retainLatest") Integer versionsToRetain,
-      // Optional parameters below
-      @QueryParam(BACKUP_LOCATION) String location,
-      @QueryParam(BACKUP_REPOSITORY) String repositoryName,
-      @QueryParam(ASYNC) String asyncId)
+      String backupName,
+      Integer versionsToRetain,
+      String location,
+      String repositoryName,
+      String asyncId)
       throws Exception {
     final var response = instantiateJerseyResponse(BackupDeletionResponseBody.class);
     recordCollectionForLogAndTracing(null, solrQueryRequest);
@@ -138,13 +123,10 @@ public class DeleteCollectionBackupAPI extends BackupAPIBase {
     return response;
   }
 
-  @Path("/backups/{backupName}/purgeUnused")
-  @PUT
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public PurgeUnusedResponse garbageCollectUnusedBackupFiles(
-      @PathParam("backupName") String backupName, PurgeUnusedFilesRequestBody requestBody)
-      throws Exception {
+      String backupName, PurgeUnusedFilesRequestBody requestBody) throws Exception {
     final var response = instantiateJerseyResponse(PurgeUnusedResponse.class);
     recordCollectionForLogAndTracing(null, solrQueryRequest);
 
@@ -163,31 +145,22 @@ public class DeleteCollectionBackupAPI extends BackupAPIBase {
             Boolean.TRUE,
             requestBody.location,
             requestBody.repositoryName,
-            requestBody.asyncId);
+            requestBody.async);
     final var remoteResponse =
         submitRemoteMessageAndHandleResponse(
             response,
             CollectionParams.CollectionAction.DELETEBACKUP,
             remoteMessage,
-            requestBody.asyncId);
+            requestBody.async);
 
     final Object remoteDeleted = remoteResponse.getResponse().get("deleted");
     if (remoteDeleted != null) {
-      response.deleted = objectMapper.convertValue(remoteDeleted, PurgeUnusedStats.class);
+      response.deleted =
+          objectMapper.convertValue(remoteDeleted, PurgeUnusedResponse.PurgeUnusedStats.class);
     }
     return response;
   }
 
-  public static class PurgeUnusedResponse extends SubResponseAccumulatingJerseyResponse {
-    @JsonProperty public PurgeUnusedStats deleted;
-  }
-
-  public static class PurgeUnusedStats implements JacksonReflectMapWriter {
-    @JsonProperty public Integer numBackupIds;
-    @JsonProperty public Integer numShardBackupIds;
-    @JsonProperty public Integer numIndexFiles;
-  }
-
   public static ZkNodeProps createRemoteMessage(
       String backupName,
       String backupId,
@@ -231,48 +204,26 @@ public class DeleteCollectionBackupAPI extends BackupAPIBase {
               BACKUP_ID));
     }
 
-    final var deleteApi = new DeleteCollectionBackupAPI(coreContainer, req, rsp);
+    final var deleteApi = new DeleteCollectionBackup(coreContainer, req, rsp);
     V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, invokeApi(deleteApi, req.getParams()));
   }
 
-  public static class BackupDeletionResponseBody extends SubResponseAccumulatingJerseyResponse {
-    @JsonProperty public String collection;
-    @JsonProperty public List<CreateCollectionBackupAPI.BackupDeletionData> deleted;
-  }
-
   @SuppressWarnings("unchecked")
-  public static List<CreateCollectionBackupAPI.BackupDeletionData> fromRemoteResponse(
+  public static List<BackupDeletionData> fromRemoteResponse(
       ObjectMapper objectMapper, SolrResponse response) {
     final var deleted = (List<SimpleOrderedMap<Object>>) response.getResponse().get("deleted");
     if (deleted == null) {
       return null;
     }
 
-    final List<CreateCollectionBackupAPI.BackupDeletionData> statList = new ArrayList<>();
+    final List<BackupDeletionData> statList = new ArrayList<>();
     for (SimpleOrderedMap<Object> remoteStat : deleted) {
-      statList.add(
-          objectMapper.convertValue(
-              remoteStat, CreateCollectionBackupAPI.BackupDeletionData.class));
+      statList.add(objectMapper.convertValue(remoteStat, BackupDeletionData.class));
     }
     return statList;
   }
 
-  /**
-   * Request body for the {@link DeleteCollectionBackupAPI#garbageCollectUnusedBackupFiles(String,
-   * PurgeUnusedFilesRequestBody)} API.
-   */
-  public static class PurgeUnusedFilesRequestBody implements JacksonReflectMapWriter {
-    @JsonProperty(BACKUP_LOCATION)
-    public String location;
-
-    @JsonProperty(BACKUP_REPOSITORY)
-    public String repositoryName;
-
-    @JsonProperty(ASYNC)
-    public String asyncId;
-  }
-
-  private static SolrJerseyResponse invokeApi(DeleteCollectionBackupAPI api, SolrParams params)
+  private static SolrJerseyResponse invokeApi(DeleteCollectionBackup api, SolrParams params)
       throws Exception {
     if (params.get(MAX_NUM_BACKUP_POINTS) != null) {
       return api.deleteMultipleBackupsByRecency(
@@ -285,7 +236,7 @@ public class DeleteCollectionBackupAPI extends BackupAPIBase {
       final var requestBody = new PurgeUnusedFilesRequestBody();
       requestBody.location = params.get(BACKUP_LOCATION);
       requestBody.repositoryName = params.get(BACKUP_REPOSITORY);
-      requestBody.asyncId = params.get(ASYNC);
+      requestBody.async = params.get(ASYNC);
       return api.garbageCollectUnusedBackupFiles(params.get(NAME), requestBody);
     } else { // BACKUP_ID != null
       return api.deleteSingleBackupById(
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
similarity index 62%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
index d77a91cafca..e591f85db6f 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteCollectionSnapshot.java
@@ -16,7 +16,6 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
 import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES;
@@ -24,19 +23,11 @@ import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
 import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
 import java.util.HashMap;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import org.apache.solr.client.api.model.AsyncJerseyResponse;
+import org.apache.solr.client.api.endpoint.DeleteCollectionSnapshotApi;
+import org.apache.solr.client.api.model.DeleteCollectionSnapshotResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.CollectionParams;
@@ -47,37 +38,23 @@ import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
 
-/** V2 API for Deleting Collection Snapshots. */
-@Path("/collections/{collName}/snapshots")
-public class DeleteCollectionSnapshotAPI extends AdminAPIBase {
+/** V2 API impl for Deleting Collection Snapshots. */
+public class DeleteCollectionSnapshot extends AdminAPIBase implements DeleteCollectionSnapshotApi {
 
   @Inject
-  public DeleteCollectionSnapshotAPI(
+  public DeleteCollectionSnapshot(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  /** This API is analogous to V1's (POST /solr/admin/collections?action=DELETESNAPSHOT) */
-  @DELETE
-  @Path("/{snapshotName}")
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
-  public DeleteSnapshotResponse deleteSnapshot(
-      @Parameter(description = "The name of the collection.", required = true)
-          @PathParam("collName")
-          String collName,
-      @Parameter(description = "The name of the snapshot to be deleted.", required = true)
-          @PathParam("snapshotName")
-          String snapshotName,
-      @Parameter(description = "A flag that treats the collName parameter as a collection alias.")
-          @DefaultValue("false")
-          @QueryParam("followAliases")
-          boolean followAliases,
-      @QueryParam("async") String asyncId)
+  public DeleteCollectionSnapshotResponse deleteSnapshot(
+      String collName, String snapshotName, boolean followAliases, String asyncId)
       throws Exception {
-    final DeleteSnapshotResponse response = instantiateJerseyResponse(DeleteSnapshotResponse.class);
+    final var response = instantiateJerseyResponse(DeleteCollectionSnapshotResponse.class);
     final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
     recordCollectionForLogAndTracing(collName, solrQueryRequest);
 
@@ -105,24 +82,6 @@ public class DeleteCollectionSnapshotAPI extends AdminAPIBase {
     return response;
   }
 
-  /**
-   * The Response for {@link DeleteCollectionSnapshotAPI}'s {@link #deleteSnapshot(String, String,
-   * boolean, String)}
-   */
-  public static class DeleteSnapshotResponse extends AsyncJerseyResponse {
-    @Schema(description = "The name of the collection.")
-    @JsonProperty(COLLECTION_PROP)
-    String collection;
-
-    @Schema(description = "The name of the snapshot to be deleted.")
-    @JsonProperty("snapshot")
-    String snapshotName;
-
-    @Schema(description = "A flag that treats the collName parameter as a collection alias.")
-    @JsonProperty("followAliases")
-    boolean followAliases;
-  }
-
   public static ZkNodeProps createRemoteMessage(
       String collectionName, boolean followAliases, String snapshotName, String asyncId) {
     final Map<String, Object> remoteMessage = new HashMap<>();
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNode.java
similarity index 70%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNode.java
index 5416793ddba..76ace8384aa 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNodeAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteNode.java
@@ -16,24 +16,17 @@
  */
 package org.apache.solr.handler.admin.api;
 
-import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
 import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
 import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
 import static org.apache.solr.common.params.CoreAdminParams.NODE;
 import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
 import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.media.Schema;
-import io.swagger.v3.oas.annotations.parameters.RequestBody;
 import java.util.HashMap;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
+import org.apache.solr.client.api.endpoint.DeleteNodeApi;
+import org.apache.solr.client.api.model.DeleteNodeRequestBody;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -42,7 +35,6 @@ import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -53,29 +45,19 @@ import org.apache.solr.response.SolrQueryResponse;
  *
  * <p>This API is analogous to the V1 /admin/collections?action=DELETENODE
  */
-@Path("cluster/nodes/{nodeName}/clear/")
-public class DeleteNodeAPI extends AdminAPIBase {
+public class DeleteNode extends AdminAPIBase implements DeleteNodeApi {
 
   @Inject
-  public DeleteNodeAPI(
+  public DeleteNode(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @POST
-  @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2})
+  @Override
   @PermissionName(COLL_EDIT_PERM)
-  public SolrJerseyResponse deleteNode(
-      @Parameter(
-              description =
-                  "The name of the node to be cleared.  Usually of the form 'host:1234_solr'.",
-              required = true)
-          @PathParam("nodeName")
-          String nodeName,
-      @RequestBody(description = "Contains user provided parameters", required = true)
-          DeleteNodeRequestBody requestBody)
+  public SolrJerseyResponse deleteNode(String nodeName, DeleteNodeRequestBody requestBody)
       throws Exception {
     final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
     final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
@@ -94,10 +76,11 @@ public class DeleteNodeAPI extends AdminAPIBase {
     return response;
   }
 
-  public static SolrJerseyResponse invokeUsingV1Inputs(DeleteNodeAPI apiInstance, SolrParams params)
+  public static SolrJerseyResponse invokeUsingV1Inputs(DeleteNode apiInstance, SolrParams params)
       throws Exception {
     final RequiredSolrParams requiredParams = params.required();
-    final DeleteNodeRequestBody requestBody = new DeleteNodeRequestBody(params.get(ASYNC));
+    final var requestBody = new DeleteNodeRequestBody();
+    requestBody.async = params.get(ASYNC);
     return apiInstance.deleteNode(requiredParams.get(NODE), requestBody);
   }
 
@@ -114,17 +97,4 @@ public class DeleteNodeAPI extends AdminAPIBase {
 
     return new ZkNodeProps(remoteMessage);
   }
-
-  public static class DeleteNodeRequestBody implements JacksonReflectMapWriter {
-
-    public DeleteNodeRequestBody() {}
-
-    public DeleteNodeRequestBody(String async) {
-      this.async = async;
-    }
-
-    @Schema(description = "Request ID to track this action which will be processed asynchronously.")
-    @JsonProperty("async")
-    public String async;
-  }
 }
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplica.java
similarity index 73%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplica.java
index 143acf95134..3f3f9d44b27 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplica.java
@@ -31,15 +31,11 @@ import static org.apache.solr.common.params.CoreAdminParams.DELETE_INSTANCE_DIR;
 import static org.apache.solr.common.params.CoreAdminParams.REPLICA;
 import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
 
-import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.HashMap;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.QueryParam;
+import org.apache.solr.client.api.endpoint.DeleteReplicaApi;
+import org.apache.solr.client.api.model.ScaleCollectionRequestBody;
 import org.apache.solr.client.api.model.SubResponseAccumulatingJerseyResponse;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -47,7 +43,6 @@ import org.apache.solr.common.params.CollectionParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.api.V2ApiUtils;
-import org.apache.solr.jersey.JacksonReflectMapWriter;
 import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
@@ -57,30 +52,28 @@ import org.apache.solr.response.SolrQueryResponse;
  *
  * <p>These APIs are analogous to the v1 /admin/collections?action=DELETEREPLICA command.
  */
-public class DeleteReplicaAPI extends AdminAPIBase {
+public class DeleteReplica extends AdminAPIBase implements DeleteReplicaApi {
 
   @Inject
-  public DeleteReplicaAPI(
+  public DeleteReplica(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
-  @DELETE
-  @Path("/collections/{collectionName}/shards/{shardName}/replicas/{replicaName}")
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse deleteReplicaByName(
-      @PathParam("collectionName") String collectionName,
-      @PathParam("shardName") String shardName,
-      @PathParam("replicaName") String replicaName,
-      // Optional params below
-      @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
-      @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
-      @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
-      @QueryParam(DELETE_INDEX) Boolean deleteIndex,
-      @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
-      @QueryParam(ASYNC) String asyncId)
+      String collectionName,
+      String shardName,
+      String replicaName, /* Optional params below */
+      Boolean followAliases,
+      Boolean deleteInstanceDir,
+      Boolean deleteDataDir,
+      Boolean deleteIndex,
+      Boolean onlyIfDown,
+      String async)
       throws Exception {
     final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
     ensureRequiredParameterProvided(COLLECTION_PROP, collectionName);
@@ -100,26 +93,24 @@ public class DeleteReplicaAPI extends AdminAPIBase {
             deleteDataDir,
             deleteIndex,
             onlyIfDown,
-            asyncId);
+            async);
     submitRemoteMessageAndHandleResponse(
-        response, CollectionParams.CollectionAction.DELETEREPLICA, remoteMessage, asyncId);
+        response, CollectionParams.CollectionAction.DELETEREPLICA, remoteMessage, async);
     return response;
   }
 
-  @DELETE
-  @Path("/collections/{collectionName}/shards/{shardName}/replicas")
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse deleteReplicasByCount(
-      @PathParam("collectionName") String collectionName,
-      @PathParam("shardName") String shardName,
-      @QueryParam(COUNT_PROP) Integer numToDelete,
-      // Optional params below
-      @QueryParam(FOLLOW_ALIASES) Boolean followAliases,
-      @QueryParam(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir,
-      @QueryParam(DELETE_DATA_DIR) Boolean deleteDataDir,
-      @QueryParam(DELETE_INDEX) Boolean deleteIndex,
-      @QueryParam(ONLY_IF_DOWN) Boolean onlyIfDown,
-      @QueryParam(ASYNC) String asyncId)
+      String collectionName,
+      String shardName,
+      Integer numToDelete,
+      Boolean followAliases,
+      Boolean deleteInstanceDir,
+      Boolean deleteDataDir,
+      Boolean deleteIndex,
+      Boolean onlyIfDown,
+      String asyncId)
       throws Exception {
     final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
     ensureRequiredParameterProvided(COLLECTION_PROP, collectionName);
@@ -145,35 +136,23 @@ public class DeleteReplicaAPI extends AdminAPIBase {
     return response;
   }
 
-  public static class ScaleCollectionRequestBody implements JacksonReflectMapWriter {
-    public @JsonProperty(value = COUNT_PROP, required = true) Integer numToDelete;
-    public @JsonProperty(FOLLOW_ALIASES) Boolean followAliases;
-    public @JsonProperty(DELETE_INSTANCE_DIR) Boolean deleteInstanceDir;
-    public @JsonProperty(DELETE_DATA_DIR) Boolean deleteDataDir;
-    public @JsonProperty(DELETE_INDEX) Boolean deleteIndex;
-    public @JsonProperty(ONLY_IF_DOWN) Boolean onlyIfDown;
-    public @JsonProperty(ASYNC) String asyncId;
-
-    public static ScaleCollectionRequestBody fromV1Params(SolrParams v1Params) {
-      final var requestBody = new ScaleCollectionRequestBody();
-      requestBody.numToDelete = v1Params.getInt(COUNT_PROP);
-      requestBody.followAliases = v1Params.getBool(FOLLOW_ALIASES);
-      requestBody.deleteInstanceDir = v1Params.getBool(DELETE_INSTANCE_DIR);
-      requestBody.deleteDataDir = v1Params.getBool(DELETE_DATA_DIR);
-      requestBody.deleteIndex = v1Params.getBool(DELETE_INDEX);
-      requestBody.onlyIfDown = v1Params.getBool(ONLY_IF_DOWN);
-      requestBody.asyncId = v1Params.get(ASYNC);
+  public static ScaleCollectionRequestBody createScaleRequestBodyFromV1Params(SolrParams v1Params) {
+    final var requestBody = new ScaleCollectionRequestBody();
+    requestBody.numToDelete = v1Params.getInt(COUNT_PROP);
+    requestBody.followAliases = v1Params.getBool(FOLLOW_ALIASES);
+    requestBody.deleteInstanceDir = v1Params.getBool(DELETE_INSTANCE_DIR);
+    requestBody.deleteDataDir = v1Params.getBool(DELETE_DATA_DIR);
+    requestBody.deleteIndex = v1Params.getBool(DELETE_INDEX);
+    requestBody.onlyIfDown = v1Params.getBool(ONLY_IF_DOWN);
+    requestBody.async = v1Params.get(ASYNC);
 
-      return requestBody;
-    }
+    return requestBody;
   }
 
-  @PUT
-  @Path("/collections/{collectionName}/scale")
+  @Override
   @PermissionName(COLL_EDIT_PERM)
   public SubResponseAccumulatingJerseyResponse deleteReplicasByCountAllShards(
-      @PathParam("collectionName") String collectionName, ScaleCollectionRequestBody requestBody)
-      throws Exception {
+      String collectionName, ScaleCollectionRequestBody requestBody) throws Exception {
     final var response = instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
     if (requestBody == null) {
       throw new SolrException(BAD_REQUEST, "Request body is required but missing");
@@ -194,12 +173,12 @@ public class DeleteReplicaAPI extends AdminAPIBase {
             requestBody.deleteDataDir,
             requestBody.deleteIndex,
             requestBody.onlyIfDown,
-            requestBody.asyncId);
+            requestBody.async);
     submitRemoteMessageAndHandleResponse(
         response,
         CollectionParams.CollectionAction.DELETEREPLICA,
         remoteMessage,
-        requestBody.asyncId);
+        requestBody.async);
     return response;
   }
 
@@ -239,13 +218,13 @@ public class DeleteReplicaAPI extends AdminAPIBase {
     v1Params.required().check(COLLECTION_PROP);
 
     final var deleteReplicaApi =
-        new DeleteReplicaAPI(coreContainer, solrQueryRequest, solrQueryResponse);
+        new DeleteReplica(coreContainer, solrQueryRequest, solrQueryResponse);
     final var v2Response = invokeApiMethod(deleteReplicaApi, v1Params);
     V2ApiUtils.squashIntoSolrResponseWithoutHeader(solrQueryResponse, v2Response);
   }
 
   private static SubResponseAccumulatingJerseyResponse invokeApiMethod(
-      DeleteReplicaAPI deleteReplicaApi, SolrParams v1Params) throws Exception {
+      DeleteReplica deleteReplicaApi, SolrParams v1Params) throws Exception {
     if (v1Params.get(REPLICA) != null && v1Params.get(SHARD_ID_PROP) != null) {
       return deleteReplicaApi.deleteReplicaByName(
           v1Params.get(COLLECTION_PROP),
@@ -271,7 +250,7 @@ public class DeleteReplicaAPI extends AdminAPIBase {
           v1Params.get(ASYNC));
     } else if (v1Params.get(COUNT_PROP) != null) {
       return deleteReplicaApi.deleteReplicasByCountAllShards(
-          v1Params.get(COLLECTION_PROP), ScaleCollectionRequestBody.fromV1Params(v1Params));
+          v1Params.get(COLLECTION_PROP), createScaleRequestBodyFromV1Params(v1Params));
     } else {
       throw new SolrException(
           BAD_REQUEST,
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaProperty.java
similarity index 77%
rename from solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java
rename to solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaProperty.java
index bce721ce087..30651f83b6c 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/DeleteReplicaProperty.java
@@ -25,11 +25,9 @@ import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP;
 import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX;
 import static org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
 
-import io.swagger.v3.oas.annotations.Parameter;
 import java.util.Map;
 import javax.inject.Inject;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
+import org.apache.solr.client.api.endpoint.DeleteReplicaPropertyApi;
 import org.apache.solr.client.api.model.SolrJerseyResponse;
 import org.apache.solr.client.solrj.SolrResponse;
 import org.apache.solr.common.cloud.ZkNodeProps;
@@ -38,41 +36,30 @@ import org.apache.solr.common.params.RequiredSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.handler.admin.CollectionsHandler;
+import org.apache.solr.jersey.PermissionName;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.PermissionNameProvider;
 
 /**
- * V2 API for removing a property from a collection replica
+ * V2 API implementation for removing a property from a collection replica
  *
- * <p>This API is analogous to the v1 /admin/collections?action=DELETEREPLICAPROP command.
+ * @see DeleteReplicaPropertyApi
  */
-@Path("/collections/{collName}/shards/{shardName}/replicas/{replicaName}/properties/{propName}")
-public class DeleteReplicaPropertyAPI extends AdminAPIBase {
+public class DeleteReplicaProperty extends AdminAPIBase implements DeleteReplicaPropertyApi {
 
   @Inject
-  public DeleteReplicaPropertyAPI(
+  public DeleteReplicaProperty(
       CoreContainer coreContainer,
       SolrQueryRequest solrQueryRequest,
       SolrQueryResponse solrQueryResponse) {
     super(coreContainer, solrQueryRequest, solrQueryResponse);
   }
 
+  @Override
+  @PermissionName(PermissionNameProvider.Name.COLL_EDIT_PERM)
   public SolrJerseyResponse deleteReplicaProperty(
-      @Parameter(
-              description = "The name of the collection the replica belongs to.",
-              required = true)
-          @PathParam("collName")
-          String collName,
-      @Parameter(description = "The name of the shard the replica belongs to.", required = true)
-          @PathParam("shardName")
-          String shardName,
-      @Parameter(description = "The replica, e.g., `core_node1`.", required = true)
-          @PathParam("replicaName")
-          String replicaName,
-      @Parameter(description = "The name of the property to delete.", required = true)
-          @PathParam("propName")
-          String propertyName)
-      throws Exception {
+      String collName, String shardName, String replicaName, String propertyName) throws Exception {
     final SolrJerseyResponse response = instantiateJerseyResponse(SolrJerseyResponse.class);
     final CoreContainer coreContainer = fetchAndValidateZooKeeperAwareCoreContainer();
     recordCollectionForLogAndTracing(collName, solrQueryRequest);
@@ -94,7 +81,7 @@ public class DeleteReplicaPropertyAPI extends AdminAPIBase {
   }
 
   public static SolrJerseyResponse invokeUsingV1Inputs(
-      DeleteReplicaPropertyAPI apiInstance, SolrParams solrParams) throws Exception {
+      DeleteReplicaProperty apiInstance, SolrParams solrParams) throws Exception {
     final RequiredSolrParams requiredParams = solrParams.required();
     final String propNameToDelete = requiredParams.get(PROPERTY_PROP);
     final String trimmedPropNameToDelete =
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java
index 02b90ba8384..7d083d1c0cf 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionBackupAPITest.java
@@ -27,10 +27,11 @@ import static org.apache.solr.common.params.CoreAdminParams.BACKUP_REPOSITORY;
 import static org.apache.solr.common.params.CoreAdminParams.MAX_NUM_BACKUP_POINTS;
 
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.PurgeUnusedFilesRequestBody;
 import org.apache.solr.common.SolrException;
 import org.junit.Test;
 
-/** Unit tests for {@link DeleteCollectionBackupAPI} */
+/** Unit tests for {@link DeleteCollectionBackup} */
 public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
   @Test
   public void testReportsErrorIfBackupNameMissing() {
@@ -40,7 +41,7 @@ public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
           expectThrows(
               SolrException.class,
               () -> {
-                final var api = new DeleteCollectionBackupAPI(null, null, null);
+                final var api = new DeleteCollectionBackup(null, null, null);
                 api.deleteSingleBackupById(
                     null, "someBackupId", "someLocation", "someRepository", "someAsyncId");
               });
@@ -55,7 +56,7 @@ public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
           expectThrows(
               SolrException.class,
               () -> {
-                final var api = new DeleteCollectionBackupAPI(null, null, null);
+                final var api = new DeleteCollectionBackup(null, null, null);
                 api.deleteMultipleBackupsByRecency(
                     null, 123, "someLocation", "someRepository", "someAsyncId");
               });
@@ -66,15 +67,15 @@ public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
 
     // Garbage collect unused files
     {
-      final var requestBody = new DeleteCollectionBackupAPI.PurgeUnusedFilesRequestBody();
+      final var requestBody = new PurgeUnusedFilesRequestBody();
       requestBody.location = "someLocation";
       requestBody.repositoryName = "someRepository";
-      requestBody.asyncId = "someAsyncId";
+      requestBody.async = "someAsyncId";
       final SolrException thrown =
           expectThrows(
               SolrException.class,
               () -> {
-                final var api = new DeleteCollectionBackupAPI(null, null, null);
+                final var api = new DeleteCollectionBackup(null, null, null);
                 api.garbageCollectUnusedBackupFiles(null, requestBody);
               });
 
@@ -89,7 +90,7 @@ public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new DeleteCollectionBackupAPI(null, null, null);
+              final var api = new DeleteCollectionBackup(null, null, null);
               api.deleteSingleBackupById(
                   "someBackupName", null, "someLocation", "someRepository", "someAsyncId");
             });
@@ -104,7 +105,7 @@ public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new DeleteCollectionBackupAPI(null, null, null);
+              final var api = new DeleteCollectionBackup(null, null, null);
               api.deleteMultipleBackupsByRecency(
                   "someBackupName", null, "someLocation", "someRepository", "someAsyncId");
             });
@@ -118,7 +119,7 @@ public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
   @Test
   public void testCreateRemoteMessageAllParams() {
     final var remoteMessage =
-        DeleteCollectionBackupAPI.createRemoteMessage(
+        DeleteCollectionBackup.createRemoteMessage(
                 "someBackupName",
                 "someBackupId",
                 123,
@@ -142,7 +143,7 @@ public class DeleteCollectionBackupAPITest extends SolrTestCaseJ4 {
   @Test
   public void testCreateRemoteMessageOnlyRequiredParams() {
     final var remoteMessage =
-        DeleteCollectionBackupAPI.createRemoteMessage(
+        DeleteCollectionBackup.createRemoteMessage(
                 "someBackupName", "someBackupId", null, null, null, null, null)
             .getProperties();
 
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java
index ac363a02bae..d6e1769e09c 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteCollectionSnapshotAPITest.java
@@ -34,8 +34,7 @@ public class DeleteCollectionSnapshotAPITest extends SolrTestCaseJ4 {
   @Test
   public void testConstructsValidOverseerMessage() {
     final ZkNodeProps messageOne =
-        DeleteCollectionSnapshotAPI.createRemoteMessage(
-            "myCollName", false, "mySnapshotName", null);
+        DeleteCollectionSnapshot.createRemoteMessage("myCollName", false, "mySnapshotName", null);
     final Map<String, Object> rawMessageOne = messageOne.getProperties();
     assertEquals(4, rawMessageOne.size());
     MatcherAssert.assertThat(
@@ -48,7 +47,7 @@ public class DeleteCollectionSnapshotAPITest extends SolrTestCaseJ4 {
     assertEquals(false, rawMessageOne.get(FOLLOW_ALIASES));
 
     final ZkNodeProps messageTwo =
-        DeleteCollectionSnapshotAPI.createRemoteMessage(
+        DeleteCollectionSnapshot.createRemoteMessage(
             "myCollName", true, "mySnapshotName", "myAsyncId");
     final Map<String, Object> rawMessageTwo = messageTwo.getProperties();
     assertEquals(5, rawMessageTwo.size());
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java
index f306881bf41..74b6b3e927b 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteNodeAPITest.java
@@ -20,13 +20,14 @@ import static org.mockito.Mockito.mock;
 
 import java.util.Map;
 import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.api.model.DeleteNodeRequestBody;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
-/** Unit tests for {@link DeleteNodeAPI} */
+/** Unit tests for {@link DeleteNode} */
 public class DeleteNodeAPITest extends SolrTestCaseJ4 {
 
   @BeforeClass
@@ -36,22 +37,22 @@ public class DeleteNodeAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
-    final var api = mock(DeleteNodeAPI.class);
+    final var api = mock(DeleteNode.class);
     final SolrException e =
         expectThrows(
             SolrException.class,
             () -> {
-              DeleteNodeAPI.invokeUsingV1Inputs(api, new ModifiableSolrParams());
+              DeleteNode.invokeUsingV1Inputs(api, new ModifiableSolrParams());
             });
     assertEquals("Missing required parameter: node", e.getMessage());
   }
 
   @Test
   public void testValidOverseerMessageIsCreated() {
-    DeleteNodeAPI.DeleteNodeRequestBody requestBody =
-        new DeleteNodeAPI.DeleteNodeRequestBody("async");
+    final var requestBody = new DeleteNodeRequestBody();
+    requestBody.async = "async";
     final ZkNodeProps createdMessage =
-        DeleteNodeAPI.createRemoteMessage("nodeNameToDelete", requestBody);
+        DeleteNode.createRemoteMessage("nodeNameToDelete", requestBody);
     final Map<String, Object> createdMessageProps = createdMessage.getProperties();
     assertEquals(3, createdMessageProps.size());
     assertEquals("nodeNameToDelete", createdMessageProps.get("node"));
@@ -61,7 +62,7 @@ public class DeleteNodeAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testRequestBodyCanBeOmitted() throws Exception {
-    final ZkNodeProps createdMessage = DeleteNodeAPI.createRemoteMessage("nodeNameToDelete", null);
+    final ZkNodeProps createdMessage = DeleteNode.createRemoteMessage("nodeNameToDelete", null);
     final Map<String, Object> createdMessageProps = createdMessage.getProperties();
     assertEquals(2, createdMessageProps.size());
     assertEquals("nodeNameToDelete", createdMessageProps.get("node"));
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java
index 3c56f7d189f..d0dca0bce8e 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaAPITest.java
@@ -33,7 +33,7 @@ import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
 import org.junit.Test;
 
-/** Unit tests for {@link DeleteReplicaAPI} */
+/** Unit tests for {@link DeleteReplica} */
 public class DeleteReplicaAPITest extends SolrTestCaseJ4 {
   @Test
   public void testReportsErrorIfCollectionNameMissing() {
@@ -41,7 +41,7 @@ public class DeleteReplicaAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new DeleteReplicaAPI(null, null, null);
+              final var api = new DeleteReplica(null, null, null);
               api.deleteReplicaByName(
                   null, "someShard", "someReplica", null, null, null, null, null, null);
             });
@@ -56,7 +56,7 @@ public class DeleteReplicaAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new DeleteReplicaAPI(null, null, null);
+              final var api = new DeleteReplica(null, null, null);
               api.deleteReplicaByName(
                   "someCollection", null, "someReplica", null, null, null, null, null, null);
             });
@@ -71,7 +71,7 @@ public class DeleteReplicaAPITest extends SolrTestCaseJ4 {
         expectThrows(
             SolrException.class,
             () -> {
-              final var api = new DeleteReplicaAPI(null, null, null);
+              final var api = new DeleteReplica(null, null, null);
               api.deleteReplicaByName(
                   "someCollection", "someShard", null, null, null, null, null, null, null);
             });
@@ -83,7 +83,7 @@ public class DeleteReplicaAPITest extends SolrTestCaseJ4 {
   @Test
   public void testCreateRemoteMessageAllProperties() {
     final var remoteMessage =
-        DeleteReplicaAPI.createRemoteMessage(
+        DeleteReplica.createRemoteMessage(
                 "someCollName",
                 "someShardName",
                 "someReplicaName",
@@ -113,7 +113,7 @@ public class DeleteReplicaAPITest extends SolrTestCaseJ4 {
   @Test
   public void testMissingValuesExcludedFromRemoteMessage() {
     final var remoteMessage =
-        DeleteReplicaAPI.createRemoteMessage(
+        DeleteReplica.createRemoteMessage(
                 "someCollName",
                 "someShardName",
                 "someReplicaName",
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java
index d03193072e0..19c85b85481 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/api/DeleteReplicaPropertyAPITest.java
@@ -34,7 +34,7 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
- * Unit tests for {@link DeleteReplicaPropertyAPI}
+ * Unit tests for {@link DeleteReplicaProperty}
  *
  * <p>End-to-end functionality is tested implicitly through v1 integration tests, so the unit tests
  * here focus primarily on how the v1 code invokes the v2 API and how the v2 API crafts its
@@ -49,7 +49,7 @@ public class DeleteReplicaPropertyAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testV1InvocationThrowsErrorsIfRequiredParametersMissing() {
-    final var api = mock(DeleteReplicaPropertyAPI.class);
+    final var api = mock(DeleteReplicaProperty.class);
     final var allParams = new ModifiableSolrParams();
     allParams.add(COLLECTION_PROP, "someColl");
     allParams.add(SHARD_ID_PROP, "someShard");
@@ -63,7 +63,7 @@ public class DeleteReplicaPropertyAPITest extends SolrTestCaseJ4 {
           expectThrows(
               SolrException.class,
               () -> {
-                DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noCollectionParams);
+                DeleteReplicaProperty.invokeUsingV1Inputs(api, noCollectionParams);
               });
       assertEquals("Missing required parameter: " + COLLECTION_PROP, e.getMessage());
     }
@@ -75,7 +75,7 @@ public class DeleteReplicaPropertyAPITest extends SolrTestCaseJ4 {
           expectThrows(
               SolrException.class,
               () -> {
-                DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noShardParams);
+                DeleteReplicaProperty.invokeUsingV1Inputs(api, noShardParams);
               });
       assertEquals("Missing required parameter: " + SHARD_ID_PROP, e.getMessage());
     }
@@ -87,7 +87,7 @@ public class DeleteReplicaPropertyAPITest extends SolrTestCaseJ4 {
           expectThrows(
               SolrException.class,
               () -> {
-                DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noReplicaParams);
+                DeleteReplicaProperty.invokeUsingV1Inputs(api, noReplicaParams);
               });
       assertEquals("Missing required parameter: " + REPLICA_PROP, e.getMessage());
     }
@@ -99,7 +99,7 @@ public class DeleteReplicaPropertyAPITest extends SolrTestCaseJ4 {
           expectThrows(
               SolrException.class,
               () -> {
-                DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, noPropertyParams);
+                DeleteReplicaProperty.invokeUsingV1Inputs(api, noPropertyParams);
               });
       assertEquals("Missing required parameter: " + PROPERTY_PROP, e.getMessage());
     }
@@ -107,28 +107,28 @@ public class DeleteReplicaPropertyAPITest extends SolrTestCaseJ4 {
 
   @Test
   public void testV1InvocationPassesAllProvidedParameters() throws Exception {
-    final var api = mock(DeleteReplicaPropertyAPI.class);
+    final var api = mock(DeleteReplicaProperty.class);
     final var allParams = new ModifiableSolrParams();
     allParams.add(COLLECTION_PROP, "someColl");
     allParams.add(SHARD_ID_PROP, "someShard");
     allParams.add(REPLICA_PROP, "someReplica");
     allParams.add(PROPERTY_PROP, "somePropName");
 
-    DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, allParams);
+    DeleteReplicaProperty.invokeUsingV1Inputs(api, allParams);
 
     verify(api).deleteReplicaProperty("someColl", "someShard", "someReplica", "somePropName");
   }
 
   @Test
   public void testV1InvocationTrimsPropertyNamePrefixIfProvided() throws Exception {
-    final var api = mock(DeleteReplicaPropertyAPI.class);
+    final var api = mock(DeleteReplicaProperty.class);
     final var allParams = new ModifiableSolrParams();
     allParams.add(COLLECTION_PROP, "someColl");
     allParams.add(SHARD_ID_PROP, "someShard");
     allParams.add(REPLICA_PROP, "someReplica");
     allParams.add(PROPERTY_PROP, "property.somePropName"); // NOTE THE "property." prefix!
 
-    DeleteReplicaPropertyAPI.invokeUsingV1Inputs(api, allParams);
+    DeleteReplicaProperty.invokeUsingV1Inputs(api, allParams);
 
     verify(api).deleteReplicaProperty("someColl", "someShard", "someReplica", "somePropName");
   }
@@ -136,7 +136,7 @@ public class DeleteReplicaPropertyAPITest extends SolrTestCaseJ4 {
   @Test
   public void testRPCMessageCreation() {
     final ZkNodeProps message =
-        DeleteReplicaPropertyAPI.createRemoteMessage(
+        DeleteReplicaProperty.createRemoteMessage(
             "someColl", "someShard", "someReplica", "somePropName");
     final Map<String, Object> props = message.getProperties();
 
diff --git a/solr/solrj/src/resources/java-template/api.mustache b/solr/solrj/src/resources/java-template/api.mustache
index 872e16f509d..bf53b49bdd5 100644
--- a/solr/solrj/src/resources/java-template/api.mustache
+++ b/solr/solrj/src/resources/java-template/api.mustache
@@ -53,16 +53,18 @@ public class {{classname}} {
         }
 
         public static class {{operationIdCamelCase}} extends SolrRequest<{{operationIdCamelCase}}Response> {
-            {{#requiredParams}}
-            {{#isBodyParam}}
+            {{#bodyParam}}
             private final {{{dataType}}} requestBody;
-            {{/isBodyParam}}
+            {{/bodyParam}}
+            {{#requiredParams}}
             {{^isBodyParam}}
             private final {{{dataType}}} {{paramName}};
             {{/isBodyParam}}
             {{/requiredParams}}
             {{#optionalParams}}
+            {{^isBodyParam}}
             private {{{dataType}}} {{paramName}};
+            {{/isBodyParam}}
             {{/optionalParams}}
 
             /**
@@ -82,14 +84,15 @@ public class {{classname}} {
                 {{^isBodyParam}}
                     this.{{paramName}} = {{paramName}};
                 {{/isBodyParam}}
-                {{#isBodyParam}}
+                {{/requiredParams}}
+                {{#bodyParam}}
                     this.requestBody = new {{baseName}}();
                     addHeader("Content-type", "application/json");
-                {{/isBodyParam}}
-                {{/requiredParams}}
+                {{/bodyParam}}
             }
 
             {{#optionalParams}}
+            {{^isBodyParam}}
             {{#description}}
             /**
              * @param {{paramName}} {{description}}
@@ -99,6 +102,7 @@ public class {{classname}} {
                 this.{{paramName}} = {{paramName}};
             }
 
+            {{/isBodyParam}}
             {{/optionalParams}}
 
             {{#bodyParam}}