diff --git a/cli/adapter/PndAdapter.go b/cli/adapter/PndAdapter.go
index a02ac56ea36adfc4f457dd03484b76fbf1d8e8fa..949b6cb103bb13005d379fb74cc80502e6e85d9c 100644
--- a/cli/adapter/PndAdapter.go
+++ b/cli/adapter/PndAdapter.go
@@ -75,7 +75,8 @@ func (p *PndAdapter) GetDevice(ctx context.Context, identifier string) (*ppb.Get
 }
 
 // GetDevices requests all devices belonging to the PrincipalNetworkDomain
-// attached to this adapter.
+// attached to this adapter. The requested devices also contain their config
+// information as gNMI notifications.
 func (p *PndAdapter) GetDevices(ctx context.Context) (*ppb.GetOndListResponse, error) {
 	resp, err := api.GetDevices(ctx, p.endpoint, p.id.String())
 	if err != nil {
@@ -84,6 +85,17 @@ func (p *PndAdapter) GetDevices(ctx context.Context) (*ppb.GetOndListResponse, e
 	return resp, nil
 }
 
+// GetFlattenedDevices requests all devices belonging to the PrincipalNetworkDomain
+// attached to this adapter. The devices do not contain the config information
+// as gNMI notifications.
+func (p *PndAdapter) GetFlattenedDevices(ctx context.Context) (*ppb.GetFlattenedOndListResponse, error) {
+	resp, err := api.GetFlattenedDevices(ctx, p.endpoint, p.id.String())
+	if err != nil {
+		return nil, err
+	}
+	return resp, nil
+}
+
 // RemoveDevice removes a device from the controller
 func (p *PndAdapter) RemoveDevice(ctx context.Context, did uuid.UUID) (*ppb.DeleteOndResponse, error) {
 	resp, err := api.DeleteDevice(ctx, p.endpoint, p.id.String(), did.String())
diff --git a/cli/cmd/deviceList.go b/cli/cmd/deviceList.go
index 9fc5247f3af6dff3d7df99c6021af666428aeaf3..d4ecad0441f06af0a7f15fb09cfe2a51f33c35be 100644
--- a/cli/cmd/deviceList.go
+++ b/cli/cmd/deviceList.go
@@ -47,7 +47,7 @@ var deviceListCmd = &cobra.Command{
 	RunE: func(cmd *cobra.Command, args []string) error {
 		spinner, _ := pterm.DefaultSpinner.Start("Fetching data from controller")
 
-		resp, err := pndAdapter.GetDevices(createContextWithAuthorization())
+		resp, err := pndAdapter.GetFlattenedDevices(createContextWithAuthorization())
 		if err != nil {
 			spinner.Fail(err)
 			return err
diff --git a/cli/cmd/prompt.go b/cli/cmd/prompt.go
index 58246f531456468f0701cb1d1394891b63a4571f..7de142ae25eaca8154b097a1b6b4b040c7c1cfcb 100644
--- a/cli/cmd/prompt.go
+++ b/cli/cmd/prompt.go
@@ -240,7 +240,7 @@ func completionBasedOnCmd(c *PromptCompleter, cmd *cobra.Command, inputSplit []s
 // the result is converted into a prompt.Suggest slice.
 func getDevices() ([]prompt.Suggest, error) {
 	spinner, _ := pterm.DefaultSpinner.Start("Fetching devices from controller.")
-	resp, err := pndAdapter.GetDevices(createContextWithAuthorization())
+	resp, err := pndAdapter.GetFlattenedDevices(createContextWithAuthorization())
 	if err != nil {
 		spinner.Fail(err)
 		return []prompt.Suggest{}, err
diff --git a/controller/api/device.go b/controller/api/device.go
index 22f51cf7d71e4330fa9c4e061f19b74083dbdd44..7793186069db8814cd554502a46a529f11fca888 100644
--- a/controller/api/device.go
+++ b/controller/api/device.go
@@ -135,6 +135,22 @@ func GetDevices(ctx context.Context, addr, pid string) (*ppb.GetOndListResponse,
 	return pndClient.GetOndList(ctx, req)
 }
 
+// GetDevices requests all devices belonging to a given
+// PrincipalNetworkDomain from the controller.
+func GetFlattenedDevices(ctx context.Context, addr, pid string) (*ppb.GetFlattenedOndListResponse, error) {
+	pndClient, err := nbi.PndClient(addr, dialOptions...)
+	if err != nil {
+		return nil, err
+	}
+
+	req := &ppb.GetOndListRequest{
+		Timestamp: time.Now().UnixNano(),
+		Pid:       pid,
+	}
+
+	return pndClient.GetFlattenedOndList(ctx, req)
+}
+
 // GetPath requests a specific path
 func GetPath(ctx context.Context, addr, pid, did, path string) (*ppb.GetPathResponse, error) {
 	pndClient, err := nbi.PndClient(addr, dialOptions...)
diff --git a/controller/controller.go b/controller/controller.go
index a1a8333c6a01fc30d5709ca918d05114423cbe0b..d40ac00e67b896761d1e1b1653dd4e37ee51c82f 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -196,6 +196,7 @@ func ensureDefaultRoleExists() error {
 			"/gosdn.rbac.RoleService/DeleteRoles",
 			"/gosdn.pnd.PndService/GetOnd",
 			"/gosdn.pnd.PndService/GetOndList",
+			"/gosdn.pnd.PndService/GetFlattenedOndList",
 			"/gosdn.pnd.PndService/GetSbi",
 			"/gosdn.pnd.PndService/GetSbiList",
 			"/gosdn.pnd.PndService/GetPath",
diff --git a/controller/northbound/server/pnd.go b/controller/northbound/server/pnd.go
index 20133de7c4f2b1f85e454505b6c3825d251c2fbf..2876b063dc1d35294c9da704e88317ccb88dce0e 100644
--- a/controller/northbound/server/pnd.go
+++ b/controller/northbound/server/pnd.go
@@ -114,6 +114,47 @@ func (p PndServer) GetOndList(ctx context.Context, request *ppb.GetOndListReques
 	}, nil
 }
 
+// GetOndList returns a list of existing onds
+func (p PndServer) GetFlattenedOndList(ctx context.Context, request *ppb.GetOndListRequest) (*ppb.GetFlattenedOndListResponse, error) {
+	labels := prometheus.Labels{"service": "pnd", "rpc": "get"}
+	start := metrics.StartHook(labels, grpcRequestsTotal)
+	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
+	pid, err := uuid.Parse(request.Pid)
+	if err != nil {
+		return nil, handleRPCError(labels, err)
+	}
+
+	pnd, err := p.pndStore.Get(store.Query{ID: pid})
+	if err != nil {
+		log.Error(err)
+		return nil, status.Errorf(codes.Aborted, "%v", err)
+	}
+
+	onds := pnd.Devices()
+	ondsBySpecificPath := make([]*ppb.FlattenedOrchestratedNetworkingDevice, len(onds))
+	for i, ond := range onds {
+		ondFlattened := &ppb.FlattenedOrchestratedNetworkingDevice{
+			Id:   ond.ID().String(),
+			Name: ond.Name(),
+			Sbi: &spb.SouthboundInterface{
+				Id:   ond.SBI().ID().String(),
+				Type: ond.SBI().Type(),
+			},
+		}
+		ondsBySpecificPath[i] = ondFlattened
+	}
+
+	return &ppb.GetFlattenedOndListResponse{
+		Timestamp: time.Now().UnixNano(),
+		Pnd: &ppb.PrincipalNetworkDomain{
+			Id:          pnd.ID().String(),
+			Name:        pnd.GetName(),
+			Description: pnd.GetDescription(),
+		},
+		Ond: ondsBySpecificPath,
+	}, nil
+}
+
 func fillOndBySpecificPath(pnd networkdomain.NetworkDomain, d device.Device, path string) (*ppb.OrchestratedNetworkingDevice, error) {
 	gnmiPath, err := ygot.StringToStructuredPath(path)
 	if err != nil {