diff --git a/api/proto/common/commands/editor_commands.proto b/api/proto/common/commands/editor_commands.proto
index 7718256f53..9551e01b7d 100644
--- a/api/proto/common/commands/editor_commands.proto
+++ b/api/proto/common/commands/editor_commands.proto
@@ -326,6 +326,47 @@ message GetBoundingBoxResponse
   repeated kiapi.common.types.Box2 boxes = 2;
 }
 
+// Retrieves a list of items.  Returns SelectionResponse
+message GetSelection
+{
+  // Specifies which document to query for selected items.
+  kiapi.common.types.ItemHeader header = 1;
+
+  // An optional list of types to filter on.
+  // If none are provided, all selected items will be returned.
+  repeated kiapi.common.types.KiCadObjectType types = 2;
+}
+
+// The set of currently selected items
+message SelectionResponse
+{
+  repeated google.protobuf.Any items = 1;
+}
+
+// Adds the given items to the selection.  Returns SelectionResponse
+message AddToSelection
+{
+  kiapi.common.types.ItemHeader header = 1;
+
+  // The items to select
+  repeated kiapi.common.types.KIID items = 2;
+}
+
+// Removes the given items to the selection.  Returns SelectionResponse
+message RemoveFromSelection
+{
+  kiapi.common.types.ItemHeader header = 1;
+
+  // The items to deselect
+  repeated kiapi.common.types.KIID items = 2;
+}
+
+// Removes all items from selection
+message ClearSelection
+{
+  kiapi.common.types.ItemHeader header = 1;
+}
+
 // Tests if a certain point falls within tolerance of an item's geometry
 message HitTest
 {
diff --git a/pcbnew/api/api_handler_pcb.cpp b/pcbnew/api/api_handler_pcb.cpp
index 67734701a1..c859b541b2 100644
--- a/pcbnew/api/api_handler_pcb.cpp
+++ b/pcbnew/api/api_handler_pcb.cpp
@@ -65,6 +65,12 @@ API_HANDLER_PCB::API_HANDLER_PCB( PCB_EDIT_FRAME* aFrame ) :
 
     registerHandler<GetItems, GetItemsResponse>( &API_HANDLER_PCB::handleGetItems );
 
+    registerHandler<GetSelection, SelectionResponse>( &API_HANDLER_PCB::handleGetSelection );
+    registerHandler<ClearSelection, Empty>( &API_HANDLER_PCB::handleClearSelection );
+    registerHandler<AddToSelection, SelectionResponse>( &API_HANDLER_PCB::handleAddToSelection );
+    registerHandler<RemoveFromSelection, SelectionResponse>(
+            &API_HANDLER_PCB::handleRemoveFromSelection );
+
     registerHandler<GetBoardStackup, BoardStackupResponse>( &API_HANDLER_PCB::handleGetStackup );
     registerHandler<GetGraphicsDefaults, GraphicsDefaultsResponse>(
             &API_HANDLER_PCB::handleGetGraphicsDefaults );
@@ -657,6 +663,141 @@ std::optional<EDA_ITEM*> API_HANDLER_PCB::getItemFromDocument( const DocumentSpe
 }
 
 
+HANDLER_RESULT<SelectionResponse> API_HANDLER_PCB::handleGetSelection(
+            const HANDLER_CONTEXT<GetSelection>& aCtx )
+{
+    if( std::optional<ApiResponseStatus> busy = checkForBusy() )
+        return tl::unexpected( *busy );
+
+    if( !validateItemHeaderDocument( aCtx.Request.header() ) )
+    {
+        ApiResponseStatus e;
+        // No message needed for AS_UNHANDLED; this is an internal flag for the API server
+        e.set_status( ApiStatusCode::AS_UNHANDLED );
+        return tl::unexpected( e );
+    }
+
+    std::set<KICAD_T> filter;
+
+    for( int typeRaw : aCtx.Request.types() )
+    {
+        auto typeMessage = static_cast<types::KiCadObjectType>( typeRaw );
+        KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
+
+        if( type == TYPE_NOT_INIT )
+            continue;
+
+        filter.insert( type );
+    }
+
+    TOOL_MANAGER* mgr = frame()->GetToolManager();
+    PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
+
+    SelectionResponse response;
+
+    for( EDA_ITEM* item : selectionTool->GetSelection() )
+    {
+        if( filter.empty() || filter.contains( item->Type() ) )
+            item->Serialize( *response.add_items() );
+    }
+
+    return response;
+}
+
+
+HANDLER_RESULT<Empty> API_HANDLER_PCB::handleClearSelection(
+        const HANDLER_CONTEXT<ClearSelection>& aCtx )
+{
+    if( std::optional<ApiResponseStatus> busy = checkForBusy() )
+        return tl::unexpected( *busy );
+
+    if( !validateItemHeaderDocument( aCtx.Request.header() ) )
+    {
+        ApiResponseStatus e;
+        // No message needed for AS_UNHANDLED; this is an internal flag for the API server
+        e.set_status( ApiStatusCode::AS_UNHANDLED );
+        return tl::unexpected( e );
+    }
+
+    TOOL_MANAGER* mgr = frame()->GetToolManager();
+    mgr->RunAction( PCB_ACTIONS::selectionClear );
+
+    return Empty();
+}
+
+
+HANDLER_RESULT<SelectionResponse> API_HANDLER_PCB::handleAddToSelection(
+        const HANDLER_CONTEXT<AddToSelection>& aCtx )
+{
+    if( std::optional<ApiResponseStatus> busy = checkForBusy() )
+        return tl::unexpected( *busy );
+
+    if( !validateItemHeaderDocument( aCtx.Request.header() ) )
+    {
+        ApiResponseStatus e;
+        // No message needed for AS_UNHANDLED; this is an internal flag for the API server
+        e.set_status( ApiStatusCode::AS_UNHANDLED );
+        return tl::unexpected( e );
+    }
+
+    TOOL_MANAGER* mgr = frame()->GetToolManager();
+    PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
+
+    std::vector<EDA_ITEM*> toAdd;
+
+    for( const types::KIID& id : aCtx.Request.items() )
+    {
+        if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
+            toAdd.emplace_back( *item );
+    }
+
+    selectionTool->AddItemsToSel( &toAdd );
+
+    SelectionResponse response;
+
+    for( EDA_ITEM* item : selectionTool->GetSelection() )
+        item->Serialize( *response.add_items() );
+
+    return response;
+}
+
+
+HANDLER_RESULT<SelectionResponse> API_HANDLER_PCB::handleRemoveFromSelection(
+        const HANDLER_CONTEXT<RemoveFromSelection>& aCtx )
+{
+    if( std::optional<ApiResponseStatus> busy = checkForBusy() )
+        return tl::unexpected( *busy );
+
+    if( !validateItemHeaderDocument( aCtx.Request.header() ) )
+    {
+        ApiResponseStatus e;
+        // No message needed for AS_UNHANDLED; this is an internal flag for the API server
+        e.set_status( ApiStatusCode::AS_UNHANDLED );
+        return tl::unexpected( e );
+    }
+
+    TOOL_MANAGER* mgr = frame()->GetToolManager();
+    PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
+
+    std::vector<EDA_ITEM*> toRemove;
+
+    for( const types::KIID& id : aCtx.Request.items() )
+    {
+        if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
+            toRemove.emplace_back( *item );
+    }
+
+    selectionTool->RemoveItemsFromSel( &toRemove );
+
+    SelectionResponse response;
+
+    for( EDA_ITEM* item : selectionTool->GetSelection() )
+        item->Serialize( *response.add_items() );
+
+    return response;
+}
+
+
 HANDLER_RESULT<BoardStackupResponse> API_HANDLER_PCB::handleGetStackup(
         const HANDLER_CONTEXT<GetBoardStackup>& aCtx )
 {
diff --git a/pcbnew/api/api_handler_pcb.h b/pcbnew/api/api_handler_pcb.h
index f4d1a43824..ac437854cb 100644
--- a/pcbnew/api/api_handler_pcb.h
+++ b/pcbnew/api/api_handler_pcb.h
@@ -74,7 +74,20 @@ private:
     HANDLER_RESULT<commands::GetItemsResponse> handleGetItems(
             const HANDLER_CONTEXT<commands::GetItems>& aCtx );
 
-    HANDLER_RESULT<BoardStackupResponse> handleGetStackup( const HANDLER_CONTEXT<GetBoardStackup>& aCtx );
+    HANDLER_RESULT<commands::SelectionResponse> handleGetSelection(
+            const HANDLER_CONTEXT<commands::GetSelection>& aCtx );
+
+    HANDLER_RESULT<Empty> handleClearSelection(
+            const HANDLER_CONTEXT<commands::ClearSelection>& aCtx );
+
+    HANDLER_RESULT<commands::SelectionResponse> handleAddToSelection(
+            const HANDLER_CONTEXT<commands::AddToSelection>& aCtx );
+
+    HANDLER_RESULT<commands::SelectionResponse> handleRemoveFromSelection(
+            const HANDLER_CONTEXT<commands::RemoveFromSelection>& aCtx );
+
+    HANDLER_RESULT<BoardStackupResponse> handleGetStackup(
+            const HANDLER_CONTEXT<GetBoardStackup>& aCtx );
 
     HANDLER_RESULT<GraphicsDefaultsResponse> handleGetGraphicsDefaults(
             const HANDLER_CONTEXT<GetGraphicsDefaults>& aCtx );