diff --git a/doc/wbctl cancel job-submission.md b/doc/wbctl cancel job-submission.md index a16ebc1..2ce00b2 100644 --- a/doc/wbctl cancel job-submission.md +++ b/doc/wbctl cancel job-submission.md @@ -1,5 +1,5 @@
-Cancels the Job-Submission resources identified by a list of resource ids. At least one id is required.
+Cancels the Job-Submission resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl cancel job-submission [flags]
diff --git a/doc/wbctl delete images.md b/doc/wbctl delete images.md
index 3506a91..639f992 100644
--- a/doc/wbctl delete images.md	
+++ b/doc/wbctl delete images.md	
@@ -1,5 +1,5 @@
 
-Deletes the Image resources identified by a list of resource ids. At least one id is required.
+Deletes the Image resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl delete images [flags]
diff --git a/doc/wbctl delete job-submissions.md b/doc/wbctl delete job-submissions.md
index f57efa2..24266fd 100644
--- a/doc/wbctl delete job-submissions.md	
+++ b/doc/wbctl delete job-submissions.md	
@@ -1,5 +1,5 @@
 
-Deletes the Job-Submission resources identified by a list of resource ids. At least one id is required.
+Deletes the Job-Submission resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl delete job-submissions [flags]
diff --git a/doc/wbctl delete jobs.md b/doc/wbctl delete jobs.md
index e650edd..7a93307 100644
--- a/doc/wbctl delete jobs.md	
+++ b/doc/wbctl delete jobs.md	
@@ -1,5 +1,5 @@
 
-Deletes the Job resources identified by a list of resource ids. At least one id is required.
+Deletes the Job resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl delete jobs [flags]
diff --git a/doc/wbctl delete snapshots.md b/doc/wbctl delete snapshots.md
index e637b8d..8026375 100644
--- a/doc/wbctl delete snapshots.md	
+++ b/doc/wbctl delete snapshots.md	
@@ -1,5 +1,5 @@
 
-Deletes the Snapshot resources identified by a list of resource ids. At least one id is required.
+Deletes the Snapshot resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl delete snapshots [flags]
diff --git a/doc/wbctl delete volumes.md b/doc/wbctl delete volumes.md
index 87b8cbe..64da9d7 100644
--- a/doc/wbctl delete volumes.md	
+++ b/doc/wbctl delete volumes.md	
@@ -1,5 +1,5 @@
 
-Deletes the Volume resources identified by a list of resource ids. At least one id is required.
+Deletes the Volume resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl delete volumes [flags]
diff --git a/doc/wbctl delete workbenches.md b/doc/wbctl delete workbenches.md
index 7537480..87ec2ad 100644
--- a/doc/wbctl delete workbenches.md	
+++ b/doc/wbctl delete workbenches.md	
@@ -1,5 +1,5 @@
 
-Deletes the Workbench resources identified by a list of resource ids. At least one id is required.
+Deletes the Workbench resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl delete workbenches [flags]
diff --git a/doc/wbctl describe image-builds.md b/doc/wbctl describe image-builds.md
index 55de127..a6ff774 100644
--- a/doc/wbctl describe image-builds.md	
+++ b/doc/wbctl describe image-builds.md	
@@ -1,5 +1,5 @@
 
-Print detailed information about the Image-Build resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print detailed information about the Image-Build resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl describe image-builds [flags]
diff --git a/doc/wbctl describe images.md b/doc/wbctl describe images.md
index 0389906..8694a01 100644
--- a/doc/wbctl describe images.md	
+++ b/doc/wbctl describe images.md	
@@ -1,5 +1,5 @@
 
-Print detailed information about the Image resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print detailed information about the Image resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl describe images [flags]
diff --git a/doc/wbctl describe job-submissions.md b/doc/wbctl describe job-submissions.md
index 8fa346d..9b38fdb 100644
--- a/doc/wbctl describe job-submissions.md	
+++ b/doc/wbctl describe job-submissions.md	
@@ -1,5 +1,5 @@
 
-Print detailed information about the Job-Submission resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print detailed information about the Job-Submission resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl describe job-submissions [flags]
diff --git a/doc/wbctl describe jobs.md b/doc/wbctl describe jobs.md
index 1e9a544..938d26f 100644
--- a/doc/wbctl describe jobs.md	
+++ b/doc/wbctl describe jobs.md	
@@ -1,5 +1,5 @@
 
-Print detailed information about the Job resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print detailed information about the Job resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl describe jobs [flags]
diff --git a/doc/wbctl describe snapshots.md b/doc/wbctl describe snapshots.md
index d2a2d63..26f35b7 100644
--- a/doc/wbctl describe snapshots.md	
+++ b/doc/wbctl describe snapshots.md	
@@ -1,5 +1,5 @@
 
-Print detailed information about the Snapshot resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print detailed information about the Snapshot resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl describe snapshots [flags]
diff --git a/doc/wbctl describe volumes.md b/doc/wbctl describe volumes.md
index 33aaf4a..e5237a4 100644
--- a/doc/wbctl describe volumes.md	
+++ b/doc/wbctl describe volumes.md	
@@ -1,5 +1,5 @@
 
-Print detailed information about the Volume resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print detailed information about the Volume resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl describe volumes [flags]
diff --git a/doc/wbctl describe workbenches.md b/doc/wbctl describe workbenches.md
index c5a0411..b491c68 100644
--- a/doc/wbctl describe workbenches.md	
+++ b/doc/wbctl describe workbenches.md	
@@ -1,5 +1,5 @@
 
-Print detailed information about the Workbench resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print detailed information about the Workbench resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl describe workbenches [flags]
diff --git a/doc/wbctl edit image.md b/doc/wbctl edit image.md
index d904465..8b863f5 100644
--- a/doc/wbctl edit image.md	
+++ b/doc/wbctl edit image.md	
@@ -1,5 +1,5 @@
 
-Edit one Image resource identified by id. One and only one id is required.
+Edit one Image resource identified by id or name. One and only one id or name is required.
 
 Usage:
   wbctl edit image [flags]
diff --git a/doc/wbctl edit job.md b/doc/wbctl edit job.md
index 14e8dc6..d241bf5 100644
--- a/doc/wbctl edit job.md	
+++ b/doc/wbctl edit job.md	
@@ -1,5 +1,5 @@
 
-Edit one Job resource identified by id. One and only one id is required.
+Edit one Job resource identified by id or name. One and only one id or name is required.
 
 Usage:
   wbctl edit job [flags]
diff --git a/doc/wbctl edit snapshot.md b/doc/wbctl edit snapshot.md
index 3f9ac81..b037c54 100644
--- a/doc/wbctl edit snapshot.md	
+++ b/doc/wbctl edit snapshot.md	
@@ -1,5 +1,5 @@
 
-Edit one Snapshot resource identified by id. One and only one id is required.
+Edit one Snapshot resource identified by id or name. One and only one id or name is required.
 
 Usage:
   wbctl edit snapshot [flags]
diff --git a/doc/wbctl edit volume.md b/doc/wbctl edit volume.md
index 7f4eab1..2a99317 100644
--- a/doc/wbctl edit volume.md	
+++ b/doc/wbctl edit volume.md	
@@ -1,5 +1,5 @@
 
-Edit one Volume resource identified by id. One and only one id is required.
+Edit one Volume resource identified by id or name. One and only one id or name is required.
 
 Usage:
   wbctl edit volume [flags]
diff --git a/doc/wbctl edit workbench.md b/doc/wbctl edit workbench.md
index 6ee3a9b..3766394 100644
--- a/doc/wbctl edit workbench.md	
+++ b/doc/wbctl edit workbench.md	
@@ -1,5 +1,5 @@
 
-Edit one Workbench resource identified by id. One and only one id is required.
+Edit one Workbench resource identified by id or name. One and only one id or name is required.
 
 Usage:
   wbctl edit workbench [flags]
diff --git a/doc/wbctl get image-builds.md b/doc/wbctl get image-builds.md
index fd389c2..2f71298 100644
--- a/doc/wbctl get image-builds.md	
+++ b/doc/wbctl get image-builds.md	
@@ -1,5 +1,5 @@
 
-Print a table of basic information about the Image-Build resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print a table of basic information about the Image-Build resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl get image-builds [flags]
diff --git a/doc/wbctl get images.md b/doc/wbctl get images.md
index a3baec0..515b2b4 100644
--- a/doc/wbctl get images.md	
+++ b/doc/wbctl get images.md	
@@ -1,5 +1,5 @@
 
-Print a table of basic information about the Image resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print a table of basic information about the Image resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl get images [flags]
diff --git a/doc/wbctl get job-submissions.md b/doc/wbctl get job-submissions.md
index 0c159a0..c3b0920 100644
--- a/doc/wbctl get job-submissions.md	
+++ b/doc/wbctl get job-submissions.md	
@@ -1,5 +1,5 @@
 
-Print a table of basic information about the Job-Submission resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print a table of basic information about the Job-Submission resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl get job-submissions [flags]
diff --git a/doc/wbctl get jobs.md b/doc/wbctl get jobs.md
index 638d897..e057edc 100644
--- a/doc/wbctl get jobs.md	
+++ b/doc/wbctl get jobs.md	
@@ -1,5 +1,5 @@
 
-Print a table of basic information about the Job resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print a table of basic information about the Job resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl get jobs [flags]
diff --git a/doc/wbctl get snapshots.md b/doc/wbctl get snapshots.md
index cdf77a6..a7b2df7 100644
--- a/doc/wbctl get snapshots.md	
+++ b/doc/wbctl get snapshots.md	
@@ -1,5 +1,5 @@
 
-Print a table of basic information about the Snapshot resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print a table of basic information about the Snapshot resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl get snapshots [flags]
diff --git a/doc/wbctl get volumes.md b/doc/wbctl get volumes.md
index 452178c..534c876 100644
--- a/doc/wbctl get volumes.md	
+++ b/doc/wbctl get volumes.md	
@@ -1,5 +1,5 @@
 
-Print a table of basic information about the Volume resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print a table of basic information about the Volume resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl get volumes [flags]
diff --git a/doc/wbctl get workbenches.md b/doc/wbctl get workbenches.md
index c85855d..42d144c 100644
--- a/doc/wbctl get workbenches.md	
+++ b/doc/wbctl get workbenches.md	
@@ -1,5 +1,5 @@
 
-Print a table of basic information about the Workbench resources identified by an optional list of resource ids. All resources printed if no ids provided.
+Print a table of basic information about the Workbench resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.
 
 Usage:
   wbctl get workbenches [flags]
diff --git a/doc/wbctl logs image-builds.md b/doc/wbctl logs image-builds.md
index 5b7c30b..5190eae 100644
--- a/doc/wbctl logs image-builds.md	
+++ b/doc/wbctl logs image-builds.md	
@@ -1,5 +1,5 @@
 
-Print the log of the Image-Build resources identified a list of resource ids. At least one id is required.
+Print the log of the Image-Build resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl logs image-builds [flags]
diff --git a/doc/wbctl logs job-submissions.md b/doc/wbctl logs job-submissions.md
index efdae80..a977797 100644
--- a/doc/wbctl logs job-submissions.md	
+++ b/doc/wbctl logs job-submissions.md	
@@ -1,5 +1,5 @@
 
-Print the log of the Job-Submission resources identified a list of resource ids. At least one id is required.
+Print the log of the Job-Submission resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl logs job-submissions [flags]
diff --git a/doc/wbctl rebuild images.md b/doc/wbctl rebuild images.md
index 23e839b..cfd74b5 100644
--- a/doc/wbctl rebuild images.md	
+++ b/doc/wbctl rebuild images.md	
@@ -1,5 +1,5 @@
 
-Rebuild the Image resources identified by a list of resource ids. At least one id is required.
+Rebuild the Image resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl rebuild images [flags]
diff --git a/doc/wbctl start jobs.md b/doc/wbctl start jobs.md
index 395cbf8..086e201 100644
--- a/doc/wbctl start jobs.md	
+++ b/doc/wbctl start jobs.md	
@@ -1,5 +1,5 @@
 
-Start the Job-Submission resources identified by a list of resource ids. At least one id is required.
+Start the Job-Submission resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl start jobs [flags]
diff --git a/doc/wbctl start workbenches.md b/doc/wbctl start workbenches.md
index 695437b..510d296 100644
--- a/doc/wbctl start workbenches.md	
+++ b/doc/wbctl start workbenches.md	
@@ -1,5 +1,5 @@
 
-Start the Workbench resources identified by a list of resource ids. At least one id is required.
+Start the Workbench resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl start workbenches [flags]
diff --git a/doc/wbctl stop job-submission.md b/doc/wbctl stop job-submission.md
index 6960c95..b4d0f33 100644
--- a/doc/wbctl stop job-submission.md	
+++ b/doc/wbctl stop job-submission.md	
@@ -1,5 +1,5 @@
 
-Cancels the Job-Submission resources identified by a list of resource ids. At least one id is required.
+Cancels the Job-Submission resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl stop job-submission [flags]
diff --git a/doc/wbctl stop workbenches.md b/doc/wbctl stop workbenches.md
index 351f827..0a23abe 100644
--- a/doc/wbctl stop workbenches.md	
+++ b/doc/wbctl stop workbenches.md	
@@ -1,5 +1,5 @@
 
-Stop the Workbench resources identified by a list of resource ids. At least one id is required.
+Stop the Workbench resources identified by a list of resource ids or names. At least one id or name is required.
 
 Usage:
   wbctl stop workbenches [flags]
diff --git a/doc/wbctl update images.md b/doc/wbctl update images.md
index e9c198b..d6e54ab 100644
--- a/doc/wbctl update images.md	
+++ b/doc/wbctl update images.md	
@@ -1,5 +1,5 @@
 
-Updates all the Image resources identified by a list of ids with the options provided.
+Updates all the Image resources identified by a list of ids or names with the options provided.
 
 Usage:
   wbctl update images [flags]
diff --git a/doc/wbctl update jobs.md b/doc/wbctl update jobs.md
index ba75d2e..27074be 100644
--- a/doc/wbctl update jobs.md	
+++ b/doc/wbctl update jobs.md	
@@ -1,5 +1,5 @@
 
-Updates all the Job resources identified by a list of ids with the options provided.
+Updates all the Job resources identified by a list of ids or names with the options provided.
 
 Usage:
   wbctl update jobs [flags]
diff --git a/doc/wbctl update snapshots.md b/doc/wbctl update snapshots.md
index 0247ef4..146a972 100644
--- a/doc/wbctl update snapshots.md	
+++ b/doc/wbctl update snapshots.md	
@@ -1,5 +1,5 @@
 
-Updates all the Snapshot resources identified by a list of ids with the options provided.
+Updates all the Snapshot resources identified by a list of ids or names with the options provided.
 
 Usage:
   wbctl update snapshots [flags]
diff --git a/doc/wbctl update volumes.md b/doc/wbctl update volumes.md
index 773668f..1e7b8ea 100644
--- a/doc/wbctl update volumes.md	
+++ b/doc/wbctl update volumes.md	
@@ -1,5 +1,5 @@
 
-Updates all the Volume resources identified by a list of ids with the options provided.
+Updates all the Volume resources identified by a list of ids or names with the options provided.
 
 Usage:
   wbctl update volumes [flags]
diff --git a/doc/wbctl update workbenches.md b/doc/wbctl update workbenches.md
index a0e4b5b..0c6f7a5 100644
--- a/doc/wbctl update workbenches.md	
+++ b/doc/wbctl update workbenches.md	
@@ -1,5 +1,5 @@
 
-Updates all the Workbench resources identified by a list of ids with the options provided.
+Updates all the Workbench resources identified by a list of ids or names with the options provided.
 
 Usage:
   wbctl update workbenches [flags]
diff --git a/internal/cmd/cancel/cancel_test.go b/internal/cmd/cancel/cancel_test.go
index d97c9fd..4bd2047 100644
--- a/internal/cmd/cancel/cancel_test.go
+++ b/internal/cmd/cancel/cancel_test.go
@@ -35,7 +35,8 @@ const (
 	cancelTestControllerID1        = "controller-1"
 	cancelTestControllerID2        = "controller-2"
 	cancelTestGetJobSubmissionsErr = "" +
-		"GET ${URL}/postgrest/job_runs?controller_id=in.%28controller-1%2Ccontroller-2%29 failed" +
+		"GET ${URL}/postgrest/job_runs?or=%28controller_id.in.%28controller-1%2Ccontroller-2%29" +
+		"%2Cjob_name.in.%28controller-1%2Ccontroller-2%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* unexpected EOF"
 	cancelTestBulkCancelErr = "" +
@@ -51,7 +52,8 @@ const (
 	expectedInvalidEndpointErr              = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
-	cancelExpectedGetQuery = "controller_id=in.(controller-1,controller-2)"
+	cancelExpectedGetQuery = "or=(controller_id.in.(controller-1,controller-2)," +
+		"job_name.in.(controller-1,controller-2))"
 )
 
 var cancelExpectedDisplaysStoppedIDs = []string{
diff --git a/internal/cmd/create/image/image_test.go b/internal/cmd/create/image/image_test.go
index 0509696..c493c2e 100644
--- a/internal/cmd/create/image/image_test.go
+++ b/internal/cmd/create/image/image_test.go
@@ -56,6 +56,8 @@ const (
 	expectedCreateImageServerErr         = "POST ${URL}/postgrest/rpc/image_create failed\n" +
 		" Caused by:\n" +
 		"\t* 401 unauthorized"
+	expectedResolveImagesQueryOne   = `or=(controller_id.in.(img-cli-1),name.in.(img-cli-1))`
+	expectedResolveImagesQueryTwo   = `or=(controller_id.in.(img-cli-1,img-cli-2),name.in.(img-cli-1,img-cli-2))`
 	expectedUpdateImageDryRunOutput = `{
     "ids": [
         "img-cli-1",
@@ -120,7 +122,7 @@ const (
 	expectedInvalidEndpointErr = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
-	expectedInvlidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
+	expectedInvalidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
 	expectedInvalidSpecErr            = "invalid character 'i' looking for beginning of value"
@@ -374,7 +376,7 @@ func TestCreateImageCommand(t *testing.T) {
 				dataplanesResponseBody:   `error`,
 			}),
 			expected: createExpected{
-				err:            expectedInvlidDataplaneErr,
+				err:            expectedInvalidDataplaneErr,
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
 				dataplaneCalls: 1,
 			},
@@ -546,6 +548,8 @@ func TestUpdateImageCommand(t *testing.T) {
 			name:  "update success prints IDs only",
 			input: updateInput{args: []string{"img-cli-1"}},
 			mockEndpoints: getUpdateImageEndpoints(
+				expectedResolveImagesQueryOne,
+				`[{"id":"img-cli-1"}]`,
 				expectedUpdateImageRequestBodyIDsOnly,
 				http.StatusOK,
 				`[{"id":"img-a"},{"id":"img-b"}]`,
@@ -559,6 +563,8 @@ func TestUpdateImageCommand(t *testing.T) {
 			name:  "server error",
 			input: updateInput{args: []string{"img-cli-1"}},
 			mockEndpoints: getUpdateImageEndpoints(
+				expectedResolveImagesQueryOne,
+				`[{"id":"img-cli-1"}]`,
 				expectedUpdateImageRequestBodyIDsOnly,
 				http.StatusUnauthorized,
 				`unauthorized`,
@@ -594,6 +600,8 @@ func TestUpdateImageCommand(t *testing.T) {
 			name:  "ids only with empty body accepted",
 			input: updateInput{args: []string{"img-cli-1"}},
 			mockEndpoints: getUpdateImageEndpoints(
+				expectedResolveImagesQueryOne,
+				`[{"id":"img-cli-1"}]`,
 				expectedUpdateImageRequestBodyIDsOnly,
 				http.StatusOK,
 				`[{"id":"img-cli-1"}]`,
@@ -614,6 +622,8 @@ func TestUpdateImageCommand(t *testing.T) {
 				args:        []string{"img-cli-1", "img-cli-2"},
 			},
 			mockEndpoints: getUpdateImageEndpoints(
+				expectedResolveImagesQueryTwo,
+				`[{"id":"img-cli-1"},{"id":"img-cli-2"}]`,
 				expectedUpdateImageRequestBodyMapping,
 				http.StatusOK,
 				`[{"id":"img-cli-1"},{"id":"img-cli-2"}]`,
@@ -636,6 +646,8 @@ func TestUpdateImageCommand(t *testing.T) {
 				args:        []string{"img-cli-1"},
 			},
 			mockEndpoints: getUpdateImageEndpoints(
+				expectedResolveImagesQueryOne,
+				`[{"id":"img-cli-1"}]`,
 				expectedUpdateImageRequestBodyAllFlags,
 				http.StatusOK,
 				`[{"id":"img-cli-1"}]`,
@@ -806,12 +818,26 @@ func getUpdateArgs(
 }
 
 func getUpdateImageEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedRequestBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
 	return append(stubs.GetStubAuthEndpoints(),
 		[]stubs.StubHTTPEndpoint{
+			{
+				Request: stubs.StubHTTPRequest{
+					Method:          http.MethodGet,
+					URL:             postgrest.Endpoint(constants.WorkbenchEndpointImages),
+					ExpectedHeaders: stubs.StubAuthHeaders,
+					ExpectedQuery:   expectedQuery,
+				},
+				Response: stubs.StubHTTPResponse{
+					Status: http.StatusOK,
+					Body:   resolveResponseBody,
+				},
+			},
 			{
 				Request: stubs.StubHTTPRequest{
 					Method:          http.MethodPost,
diff --git a/internal/cmd/create/job/job_test.go b/internal/cmd/create/job/job_test.go
index 07aea10..252e10a 100644
--- a/internal/cmd/create/job/job_test.go
+++ b/internal/cmd/create/job/job_test.go
@@ -54,6 +54,8 @@ const (
 		`"description":"R app","services":["svc-a","svc-b"]}]`
 	expectedServicesResponseBody = `[{"id":"service-1","name":"service 1"},` +
 		`{"id":"service-2","name":"service 2"}]`
+	expectedResolveImagesQueryOne           = `or=(name.in.(job-cli-1))`
+	expectedResolveImagesQueryTwo           = `or=(name.in.(job-cli-1,job-cli-2))`
 	expectedCreateJobRequestBodyWithContext = `
 {
   "param": {
@@ -388,13 +390,15 @@ const (
 	expectedInvalidEndpointErr = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
-	expectedInvlidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
+	expectedInvalidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
-	expectedInvlidWorkspaceErr = "GET ${URL}/postgrest/workspaces?controller_id=in.%28invalid%29 failed" +
+	expectedInvalidWorkspaceErr = "GET ${URL}/postgrest/workspaces" +
+		"?or=%28controller_id.in.%28invalid%29%2Cname.in.%28invalid%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
-	expectedInvlidApplicationErr = "GET ${URL}/postgrest/dataplane_workbench_applications?id=in.%28invalid%29 failed" +
+	expectedInvalidApplicationErr = "GET ${URL}/postgrest/dataplane_workbench_applications" +
+		"?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
 	expectedCreateImageInvalidFileErr = "unexpected end of JSON input"
@@ -763,7 +767,7 @@ func TestCreateJobCommand(t *testing.T) {
 			}),
 			expected: createExpected{
 				displays:        []string{stubs.ExpectedDeviceAuthDisplay},
-				err:             expectedInvlidDataplaneErr,
+				err:             expectedInvalidDataplaneErr,
 				dataplanesCalls: 1,
 			},
 		},
@@ -775,7 +779,7 @@ func TestCreateJobCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateJobEndpoints(jobEndpointsConfig{
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(invalid)",
+					ExpectedQuery:  "or=(controller_id.in.(invalid),name.in.(invalid))",
 					ResponseBody:   `error`,
 					ResponseStatus: http.StatusBadRequest,
 				},
@@ -783,7 +787,7 @@ func TestCreateJobCommand(t *testing.T) {
 			expected: createExpected{
 				displays:        []string{stubs.ExpectedDeviceAuthDisplay},
 				workspacesCalls: 1,
-				err:             expectedInvlidWorkspaceErr,
+				err:             expectedInvalidWorkspaceErr,
 			},
 		},
 		{
@@ -802,7 +806,7 @@ func TestCreateJobCommand(t *testing.T) {
 			expected: createExpected{
 				displays:          []string{stubs.ExpectedDeviceAuthDisplay},
 				applicationsCalls: 1,
-				err:               expectedInvlidApplicationErr,
+				err:               expectedInvalidApplicationErr,
 			},
 		},
 	}
@@ -1033,6 +1037,11 @@ func TestUpdateJobCommand(t *testing.T) {
 				args: []string{"job-cli-1"},
 			},
 			mockEndpoints: getUpdateJobEndpoints(jobEndpointsConfig{
+				jobsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveImagesQueryOne,
+					ResponseBody:   `[{"id":"job-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				jobConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedUpdateJobRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusOK,
@@ -1050,6 +1059,11 @@ func TestUpdateJobCommand(t *testing.T) {
 				args: []string{"job-cli-1"},
 			},
 			mockEndpoints: getUpdateJobEndpoints(jobEndpointsConfig{
+				jobsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveImagesQueryOne,
+					ResponseBody:   `[{"id":"job-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				jobConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedUpdateJobRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusUnauthorized,
@@ -1096,6 +1110,11 @@ func TestUpdateJobCommand(t *testing.T) {
 				args:        []string{"job-cli-1", "job-cli-2"},
 			},
 			mockEndpoints: getUpdateJobEndpoints(jobEndpointsConfig{
+				jobsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveImagesQueryTwo,
+					ResponseBody:   `[{"id":"job-cli-1"},{"id":"job-cli-2"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				jobConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedUpdateJobRequestBodyMapping,
 					ResponseStatus:      http.StatusOK,
@@ -1125,6 +1144,11 @@ func TestUpdateJobCommand(t *testing.T) {
 				args:        []string{"job-cli-1"},
 			},
 			mockEndpoints: getUpdateJobEndpoints(jobEndpointsConfig{
+				jobsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveImagesQueryOne,
+					ResponseBody:   `[{"id":"job-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				jobConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedUpdateJobRequestBodyAllFlags,
 					ResponseStatus:      http.StatusOK,
@@ -1341,6 +1365,7 @@ type jobEndpointsConfig struct {
 	servicesConfig     stubs.StubHTTPPartialGetConfig
 	nodepoolsConfig    stubs.StubHTTPPartialGetConfig
 	workspaceConfig    stubs.StubHTTPPartialGetConfig
+	jobsConfig         stubs.StubHTTPPartialGetConfig
 	jobConfig          stubs.StubHTTPPartialPostConfig
 }
 
@@ -1353,6 +1378,18 @@ func getJobEndpoints(
 	endpoints = append(endpoints, stubs.GetStubApplicationsEndpoints(config.applicationsConfig)...)
 	endpoints = append(endpoints, stubs.GetStubServicesEndpoints(config.servicesConfig)...)
 	endpoints = append(endpoints, stubs.GetStubNodepoolsEndpoints(config.nodepoolsConfig)...)
+	endpoints = append(endpoints, stubs.StubHTTPEndpoint{
+		Request: stubs.StubHTTPRequest{
+			Method:          http.MethodGet,
+			URL:             postgrest.Endpoint(constants.WorkbenchEndpointJobs),
+			ExpectedHeaders: stubs.StubAuthHeaders,
+			ExpectedQuery:   config.jobsConfig.ExpectedQuery,
+		},
+		Response: stubs.StubHTTPResponse{
+			Status: config.jobsConfig.ResponseStatus,
+			Body:   config.jobsConfig.ResponseBody,
+		},
+	})
 	return append(endpoints,
 		[]stubs.StubHTTPEndpoint{
 			{
diff --git a/internal/cmd/create/job_submission/job_submission_test.go b/internal/cmd/create/job_submission/job_submission_test.go
index ede4a00..3c75d52 100644
--- a/internal/cmd/create/job_submission/job_submission_test.go
+++ b/internal/cmd/create/job_submission/job_submission_test.go
@@ -44,10 +44,11 @@ const (
 	expectedCreateJobInvalidEndpointErr            = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
-	expectedInvlidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
+	expectedInvalidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
-	expectedInvlidWorkspaceErr = "GET ${URL}/postgrest/workspaces?controller_id=in.%28invalid%29 failed" +
+	expectedInvalidWorkspaceErr = "GET ${URL}/postgrest/workspaces" +
+		"?or=%28controller_id.in.%28invalid%29%2Cname.in.%28invalid%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
 )
@@ -236,7 +237,7 @@ func TestCreateJobSubmissionCommand(t *testing.T) {
 			}),
 			expected: createExpected{
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
-				err:            expectedInvlidDataplaneErr,
+				err:            expectedInvalidDataplaneErr,
 				dataplaneCalls: 1,
 			},
 		},
@@ -254,7 +255,7 @@ func TestCreateJobSubmissionCommand(t *testing.T) {
 					ResponseStatus: http.StatusOK,
 				},
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(invalid)",
+					ExpectedQuery:  "or=(controller_id.in.(invalid),name.in.(invalid))",
 					ResponseStatus: http.StatusBadRequest,
 					ResponseBody:   `error`,
 				},
@@ -263,7 +264,7 @@ func TestCreateJobSubmissionCommand(t *testing.T) {
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
 				dataplaneCalls: 1,
 				workspaceCalls: 1,
-				err:            expectedInvlidWorkspaceErr,
+				err:            expectedInvalidWorkspaceErr,
 			},
 		},
 	}
@@ -465,7 +466,7 @@ func TestStartJobCommand(t *testing.T) {
 			}),
 			expected: createExpected{
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
-				err:            expectedInvlidDataplaneErr,
+				err:            expectedInvalidDataplaneErr,
 				dataplaneCalls: 1,
 			},
 		},
@@ -484,7 +485,7 @@ func TestStartJobCommand(t *testing.T) {
 					ResponseStatus: http.StatusOK,
 				},
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(invalid)",
+					ExpectedQuery:  "or=(controller_id.in.(invalid),name.in.(invalid))",
 					ResponseStatus: http.StatusBadRequest,
 					ResponseBody:   `error`,
 				},
@@ -493,7 +494,7 @@ func TestStartJobCommand(t *testing.T) {
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
 				dataplaneCalls: 1,
 				workspaceCalls: 1,
-				err:            expectedInvlidWorkspaceErr,
+				err:            expectedInvalidWorkspaceErr,
 			},
 		},
 	}
diff --git a/internal/cmd/create/snapshot/snapshot_test.go b/internal/cmd/create/snapshot/snapshot_test.go
index bc765c8..8a7a1ec 100644
--- a/internal/cmd/create/snapshot/snapshot_test.go
+++ b/internal/cmd/create/snapshot/snapshot_test.go
@@ -65,8 +65,9 @@ const (
         }
     }
 }`
-	snapshotUpdateMissingIDErr = "at least one resource ID must be provided for update"
-	snapshotUpdateServerErr    = "POST ${URL}/postgrest/rpc/snapshot_bulk_update failed\n" +
+	expectedResolveSnapshotsQuery = `or=(controller_id.in.(snap-cli-1),name.in.(snap-cli-1))`
+	snapshotUpdateMissingIDErr    = "at least one resource ID must be provided for update"
+	snapshotUpdateServerErr       = "POST ${URL}/postgrest/rpc/snapshot_bulk_update failed\n" +
 		" Caused by:\n" +
 		"\t* 401 unauthorized"
 	snapshotUpdateLongFlagsDryRunOutput = `{
@@ -86,11 +87,12 @@ const (
 	expectedCreateSnapshotInvalidEndpointErr = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
-	expectedInvlidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
+	expectedInvalidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
 	expectedInvalidVolumeErr = "" +
-		"failed to get volume invalid: GET ${URL}/postgrest/workspaces?controller_id=in.%28invalid%29 failed" +
+		"failed to get volume invalid: GET ${URL}/postgrest/workspaces" +
+		"?or=%28controller_id.in.%28invalid%29%2Cname.in.%28invalid%29%29 failed" +
 		"\n Caused by:" + "\n\t* 400 error"
 	expectedCreateImageInvalidFileErr = "unexpected end of JSON input"
 )
@@ -157,7 +159,7 @@ func TestCreateSnapshotCommand(t *testing.T) {
 					ResponseStatus: http.StatusOK,
 				},
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(ws-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(ws-1),name.in.(ws-1))`,
 					ResponseBody:   `[{"id":"ws-1","controller_id":"ctrl-1"}]`,
 					ResponseStatus: http.StatusOK,
 				},
@@ -189,7 +191,7 @@ func TestCreateSnapshotCommand(t *testing.T) {
 					ResponseStatus: http.StatusOK,
 				},
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(ws-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(ws-1),name.in.(ws-1))`,
 					ResponseBody:   `[{"id":"ws-1","controller_id":"ctrl-1"}]`,
 					ResponseStatus: http.StatusOK,
 				},
@@ -220,7 +222,7 @@ func TestCreateSnapshotCommand(t *testing.T) {
 					ResponseStatus: http.StatusOK,
 				},
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(ws-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(ws-1),name.in.(ws-1))`,
 					ResponseBody:   `[{"id":"ws-1","controller_id":"ctrl-1"}]`,
 					ResponseStatus: http.StatusOK,
 				},
@@ -259,7 +261,7 @@ func TestCreateSnapshotCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateSnapshotEndpoints(snapshotEndpointsConfig{
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(missing)`,
+					ExpectedQuery:  `or=(controller_id.in.(missing),name.in.(missing))`,
 					ResponseBody:   `[]`,
 					ResponseStatus: http.StatusOK,
 				},
@@ -280,7 +282,7 @@ func TestCreateSnapshotCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateSnapshotEndpoints(snapshotEndpointsConfig{
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(invalid)`,
+					ExpectedQuery:  `or=(controller_id.in.(invalid),name.in.(invalid))`,
 					ResponseBody:   `error`,
 					ResponseStatus: http.StatusBadRequest,
 				},
@@ -315,7 +317,7 @@ func TestCreateSnapshotCommand(t *testing.T) {
 			}),
 			expected: createExpected{
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
-				err:            expectedInvlidDataplaneErr,
+				err:            expectedInvalidDataplaneErr,
 				dataplaneCalls: 1,
 			},
 		},
@@ -443,7 +445,7 @@ func TestUpdateSnapshotCommand(t *testing.T) {
 			},
 			mockEndpoints: getUpdateSnapshotEndpoints(snapshotEndpointsConfig{
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(ws-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(ws-1),name.in.(ws-1))`,
 					ResponseBody:   `[{"id":"ws-1","controller_id":"ctrl-1"}]`,
 					ResponseStatus: http.StatusOK,
 				},
@@ -471,6 +473,11 @@ func TestUpdateSnapshotCommand(t *testing.T) {
 				args: []string{"snap-cli-1"},
 			},
 			mockEndpoints: getUpdateSnapshotEndpoints(snapshotEndpointsConfig{
+				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveSnapshotsQuery,
+					ResponseBody:   `[{"id":"snap-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				snapshotConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: snapshotUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusOK,
@@ -488,6 +495,11 @@ func TestUpdateSnapshotCommand(t *testing.T) {
 				args: []string{"snap-cli-1"},
 			},
 			mockEndpoints: getUpdateSnapshotEndpoints(snapshotEndpointsConfig{
+				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveSnapshotsQuery,
+					ResponseBody:   `[{"id":"snap-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				snapshotConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: snapshotUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusUnauthorized,
@@ -529,6 +541,11 @@ func TestUpdateSnapshotCommand(t *testing.T) {
 				args: []string{"snap-cli-1"},
 			},
 			mockEndpoints: getUpdateSnapshotEndpoints(snapshotEndpointsConfig{
+				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveSnapshotsQuery,
+					ResponseBody:   `[{"id":"snap-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				snapshotConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: snapshotUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusOK,
@@ -550,10 +567,15 @@ func TestUpdateSnapshotCommand(t *testing.T) {
 			},
 			mockEndpoints: getUpdateSnapshotEndpoints(snapshotEndpointsConfig{
 				workspaceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(ws-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(ws-1),name.in.(ws-1))`,
 					ResponseBody:   `[{"id":"ws-1","controller_id":"ctrl-1"}]`,
 					ResponseStatus: http.StatusOK,
 				},
+				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveSnapshotsQuery,
+					ResponseBody:   `[{"id":"snap-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				snapshotConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: snapshotUpdateRequestBodyMapping,
 					ResponseStatus:      http.StatusOK,
@@ -703,6 +725,7 @@ func getUpdateArgs(
 type snapshotEndpointsConfig struct {
 	dataplaneConfig stubs.StubHTTPPartialGetConfig
 	workspaceConfig stubs.StubHTTPPartialGetConfig
+	snapshotsConfig stubs.StubHTTPPartialGetConfig
 	snapshotConfig  stubs.StubHTTPPartialPostConfig
 }
 
@@ -712,6 +735,18 @@ func getSnapshotEndpoints(
 ) []stubs.StubHTTPEndpoint {
 	endpoints := append(stubs.GetStubAuthEndpoints(), stubs.GetStubWorkspaceEndpoints(config.workspaceConfig)...)
 	endpoints = append(endpoints, stubs.GetStubDataplaneEndpoints(config.dataplaneConfig)...)
+	endpoints = append(endpoints, stubs.StubHTTPEndpoint{
+		Request: stubs.StubHTTPRequest{
+			Method:          http.MethodGet,
+			URL:             postgrest.Endpoint(constants.WorkbenchEndpointSnapshots),
+			ExpectedHeaders: stubs.StubAuthHeaders,
+			ExpectedQuery:   config.snapshotsConfig.ExpectedQuery,
+		},
+		Response: stubs.StubHTTPResponse{
+			Status: config.snapshotsConfig.ResponseStatus,
+			Body:   config.snapshotsConfig.ResponseBody,
+		},
+	})
 	return append(endpoints,
 		[]stubs.StubHTTPEndpoint{
 			{
diff --git a/internal/cmd/create/workbench/workbench_test.go b/internal/cmd/create/workbench/workbench_test.go
index b0cc081..94d4ab4 100644
--- a/internal/cmd/create/workbench/workbench_test.go
+++ b/internal/cmd/create/workbench/workbench_test.go
@@ -112,20 +112,24 @@ const (
 	expectedCreateSnapshotInvalidEndpointErr = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
-	expectedInvlidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
+	expectedInvalidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
-	expectedInvlidWorkspaceErr = "GET ${URL}/postgrest/workspaces?controller_id=in.%28invalid%29 failed" +
+	expectedInvalidWorkspaceErr = "GET ${URL}/postgrest/workspaces" +
+		"?or=%28controller_id.in.%28invalid%29%2Cname.in.%28invalid%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
-	expectedInvlidApplicationErr = "GET ${URL}/postgrest/dataplane_workbench_applications?id=in.%28invalid%29 failed" +
+	expectedInvalidApplicationErr = "GET ${URL}/postgrest/dataplane_workbench_applications" +
+		"?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
 	expectedApplicationQuery = "id=in.(app-1)"
 	applicationResponseBody  = `[{"id":"app-1","services":["svc-a","svc-b"]}]`
 	servicesResponseBody     = `[{"id":"service-1","name":"service 1"},` +
 		`{"id":"service-2","name":"service 2"}]`
-	expectedCreateImageInvalidFileErr = "unexpected end of JSON input"
+	expectedResolveWorkbenchesQueryOne = `or=(controller_id.in.(wb-cli-1),name.in.(wb-cli-1))`
+	expectedResolveWorkbenchesQueryTwo = `or=(controller_id.in.(wb-cli-1,wb-cli-2),name.in.(wb-cli-1,wb-cli-2))`
+	expectedCreateImageInvalidFileErr  = "unexpected end of JSON input"
 )
 
 type createInput struct {
@@ -454,7 +458,7 @@ func TestCreateWorkbenchCommand(t *testing.T) {
 			}),
 			expected: createExpected{
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
-				err:            expectedInvlidDataplaneErr,
+				err:            expectedInvalidDataplaneErr,
 				dataplaneCalls: 1,
 			},
 		},
@@ -466,7 +470,7 @@ func TestCreateWorkbenchCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateWorkbenchEndpoints(workbenchEndpointsConfig{
 				workspacesConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(invalid)`,
+					ExpectedQuery:  `or=(controller_id.in.(invalid),name.in.(invalid))`,
 					ResponseStatus: http.StatusBadRequest,
 					ResponseBody:   `error`,
 				},
@@ -474,7 +478,7 @@ func TestCreateWorkbenchCommand(t *testing.T) {
 			expected: createExpected{
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
 				workspaceCalls: 1,
-				err:            expectedInvlidWorkspaceErr,
+				err:            expectedInvalidWorkspaceErr,
 			},
 		},
 		{
@@ -493,7 +497,7 @@ func TestCreateWorkbenchCommand(t *testing.T) {
 			expected: createExpected{
 				displays:          []string{stubs.ExpectedDeviceAuthDisplay},
 				applicationsCalls: 1,
-				err:               expectedInvlidApplicationErr,
+				err:               expectedInvalidApplicationErr,
 			},
 		},
 	}
@@ -733,6 +737,11 @@ func TestUpdateWorkbenchCommand(t *testing.T) {
 				args: []string{"wb-cli-1"},
 			},
 			mockEndpoints: getBulkUpdateWorkbenchEndpoints(workbenchEndpointsConfig{
+				workbenchesResolveConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkbenchesQueryOne,
+					ResponseBody:   `[{"id":"wb-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workbenchesConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workbenchUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusUnauthorized,
@@ -774,6 +783,11 @@ func TestUpdateWorkbenchCommand(t *testing.T) {
 				args: []string{"wb-cli-1"},
 			},
 			mockEndpoints: getBulkUpdateWorkbenchEndpoints(workbenchEndpointsConfig{
+				workbenchesResolveConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkbenchesQueryOne,
+					ResponseBody:   `[{"id":"wb-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workbenchesConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workbenchUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusOK,
@@ -799,6 +813,11 @@ func TestUpdateWorkbenchCommand(t *testing.T) {
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   applicationResponseBody,
 				},
+				workbenchesResolveConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkbenchesQueryTwo,
+					ResponseBody:   `[{"id":"wb-cli-1"},{"id":"wb-cli-2"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workbenchesConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workbenchUpdateRequestBodyMapping,
 					ResponseStatus:      http.StatusOK,
@@ -818,6 +837,11 @@ func TestUpdateWorkbenchCommand(t *testing.T) {
 				args:       []string{"wb-cli-1"},
 			},
 			mockEndpoints: getBulkUpdateWorkbenchEndpoints(workbenchEndpointsConfig{
+				workbenchesResolveConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkbenchesQueryOne,
+					ResponseBody:   `[{"id":"wb-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workbenchesConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workbenchUpdateRequestBodyWorkspace,
 					ResponseStatus:      http.StatusOK,
@@ -838,6 +862,11 @@ func TestUpdateWorkbenchCommand(t *testing.T) {
 				args:   []string{"wb-cli-1"},
 			},
 			mockEndpoints: getUpdateWorkbenchEndpoints(workbenchEndpointsConfig{
+				workbenchesResolveConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkbenchesQueryOne,
+					ResponseBody:   `[{"id":"wb-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workbenchesConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workbenchUpdateRequestBodyNameLabels,
 					ResponseStatus:      http.StatusOK,
@@ -1030,12 +1059,13 @@ func getUpdateArgs(
 }
 
 type workbenchEndpointsConfig struct {
-	dataplaneConfig    stubs.StubHTTPPartialGetConfig
-	applicationsConfig stubs.StubHTTPPartialGetConfig
-	servicesConfig     stubs.StubHTTPPartialGetConfig
-	nodepoolsConfig    stubs.StubHTTPPartialGetConfig
-	workspacesConfig   stubs.StubHTTPPartialGetConfig
-	workbenchesConfig  stubs.StubHTTPPartialPostConfig
+	dataplaneConfig          stubs.StubHTTPPartialGetConfig
+	applicationsConfig       stubs.StubHTTPPartialGetConfig
+	servicesConfig           stubs.StubHTTPPartialGetConfig
+	nodepoolsConfig          stubs.StubHTTPPartialGetConfig
+	workspacesConfig         stubs.StubHTTPPartialGetConfig
+	workbenchesResolveConfig stubs.StubHTTPPartialGetConfig
+	workbenchesConfig        stubs.StubHTTPPartialPostConfig
 }
 
 func getWorkbenchEndpoints(
@@ -1047,6 +1077,18 @@ func getWorkbenchEndpoints(
 	endpoints = append(endpoints, stubs.GetStubApplicationsEndpoints(config.applicationsConfig)...)
 	endpoints = append(endpoints, stubs.GetStubServicesEndpoints(config.servicesConfig)...)
 	endpoints = append(endpoints, stubs.GetStubNodepoolsEndpoints(config.nodepoolsConfig)...)
+	endpoints = append(endpoints, stubs.StubHTTPEndpoint{
+		Request: stubs.StubHTTPRequest{
+			Method:          http.MethodGet,
+			URL:             postgrest.Endpoint(constants.WorkbenchEndpointWorkbenches),
+			ExpectedHeaders: stubs.StubAuthHeaders,
+			ExpectedQuery:   config.workbenchesResolveConfig.ExpectedQuery,
+		},
+		Response: stubs.StubHTTPResponse{
+			Status: config.workbenchesResolveConfig.ResponseStatus,
+			Body:   config.workbenchesResolveConfig.ResponseBody,
+		},
+	})
 	return append(endpoints,
 		[]stubs.StubHTTPEndpoint{
 			{
@@ -1071,6 +1113,7 @@ func getCreateWorkbenchEndpoints(config workbenchEndpointsConfig) []stubs.StubHT
 func getUpdateWorkbenchEndpoints(config workbenchEndpointsConfig) []stubs.StubHTTPEndpoint {
 	return getWorkbenchEndpoints(postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkbenchUpdate), config)
 }
+
 func getBulkUpdateWorkbenchEndpoints(config workbenchEndpointsConfig) []stubs.StubHTTPEndpoint {
 	return getWorkbenchEndpoints(postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkbenchBulkUpdate), config)
 }
diff --git a/internal/cmd/create/workspace/workspace_test.go b/internal/cmd/create/workspace/workspace_test.go
index e2fe87c..128ef8c 100644
--- a/internal/cmd/create/workspace/workspace_test.go
+++ b/internal/cmd/create/workspace/workspace_test.go
@@ -86,6 +86,7 @@ const (
         }
     }
 }`
+	expectedResolveWorkspacesQueryOne = `or=(controller_id.in.(ws-cli-1),name.in.(ws-cli-1))`
 	workspaceUpdateRequestBodyIDsOnly = `{"ids":["ws-cli-1"],"workspace":{"pvc_status":{"status":{}}}}`
 	workspaceUpdateFileContent        = `{"id":"ws-file","description":"from file"}`
 	workspaceUpdateFileDryRunOutput   = `{
@@ -114,7 +115,7 @@ const (
 	expectedCreateSnapshotInvalidEndpointErr = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
-	expectedInvlidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
+	expectedInvalidDataplaneErr = "GET ${URL}/postgrest/dataplanes?id=in.%28invalid%29 failed" +
 		"\n Caused by:" +
 		"\n\t* 400 error"
 	expectedInvalidInitalizersErr = "" +
@@ -128,7 +129,8 @@ const (
 		"\n Caused by:" +
 		"\n\t* 400 error"
 	expectedInvalidSnapshotErr = "" +
-		"GET ${URL}/postgrest/snapshots?controller_id=in.%28snap-1%29 failed" +
+		"GET ${URL}/postgrest/snapshots?" +
+		"or=%28controller_id.in.%28snap-1%29%2Cname.in.%28snap-1%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* invalid character 'i' looking for beginning of value"
 )
@@ -271,7 +273,7 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 					ResponseStatus: http.StatusOK,
 				},
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(snap-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(snap-1),name.in.(snap-1))`,
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   workspaceSnapshotLookupBody,
 				},
@@ -403,7 +405,7 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateWorkspaceEndpoints(workspaceEndpointsConfig{
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(invalid-snap)`,
+					ExpectedQuery:  `or=(controller_id.in.(invalid-snap),name.in.(invalid-snap))`,
 					ResponseBody:   `[]`,
 					ResponseStatus: http.StatusOK,
 				},
@@ -423,7 +425,7 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateWorkspaceEndpoints(workspaceEndpointsConfig{
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery: `controller_id=in.(snap-not-ready)`,
+					ExpectedQuery: `or=(controller_id.in.(snap-not-ready),name.in.(snap-not-ready))`,
 					ResponseBody: `[{"id":"snap-not-ready","controller_id":"","volume_status":{"status":` +
 						`{"readyToUse":false}}}]`,
 					ResponseStatus: http.StatusOK,
@@ -445,7 +447,7 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateWorkspaceEndpoints(workspaceEndpointsConfig{
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery: `controller_id=in.(snap-no-size)`,
+					ExpectedQuery: `or=(controller_id.in.(snap-no-size),name.in.(snap-no-size))`,
 					ResponseBody: `[{"id":"snap-no-size","controller_id":"ctrl-snap","workspace_source":{` +
 						`"storage_spec":{"accessModes":["ReadWriteOnce"],"storageClassName":"snap-sc"}},` +
 						`"volume_status":{"status":{"readyToUse":true}}}]`,
@@ -468,7 +470,7 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateWorkspaceEndpoints(workspaceEndpointsConfig{
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery: `controller_id=in.(snap-1)`,
+					ExpectedQuery: `or=(controller_id.in.(snap-1),name.in.(snap-1))`,
 					ResponseBody: `[{"id":"snap-1","controller_id":"ctrl-snap","volume_status":` +
 						`{"status":{"readyToUse":true}}}]`,
 					ResponseStatus: http.StatusOK,
@@ -490,7 +492,7 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateWorkspaceEndpoints(workspaceEndpointsConfig{
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery: `controller_id=in.(snap-1)`,
+					ExpectedQuery: `or=(controller_id.in.(snap-1),name.in.(snap-1))`,
 					ResponseBody: `[{"id":"snap-1","controller_id":"ctrl-snap","workspace_source":{"storage_spec":` +
 						`{"accessModes":[],"storageClassName":"snap-sc"}},"volume_status":{"status":` +
 						`{"readyToUse":true,"restoreSize":"1Gi"}}}]`,
@@ -528,14 +530,14 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 					ResponseBody:   `error`,
 				},
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(snap-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(snap-1),name.in.(snap-1))`,
 					ResponseBody:   workspaceSnapshotLookupBody,
 					ResponseStatus: http.StatusOK,
 				},
 			}),
 			expected: createExpected{
 				displays:       []string{stubs.ExpectedDeviceAuthDisplay},
-				err:            expectedInvlidDataplaneErr,
+				err:            expectedInvalidDataplaneErr,
 				dataplaneCalls: 1,
 				snapshotCalls:  1,
 			},
@@ -591,7 +593,7 @@ func TestCreateWorkspaceCommand(t *testing.T) {
 			},
 			mockEndpoints: getCreateWorkspaceEndpoints(workspaceEndpointsConfig{
 				snapshotsConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  `controller_id=in.(snap-1)`,
+					ExpectedQuery:  `or=(controller_id.in.(snap-1),name.in.(snap-1))`,
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   `[invalid`,
 				},
@@ -771,6 +773,11 @@ func TestUpdateWorkspaceCommand(t *testing.T) {
 				args: []string{"ws-cli-1"},
 			},
 			mockEndpoints: getBulkUpdateWorkspaceEndpoints(workspaceUpdateEndpointsConfig{
+				workspacesConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkspacesQueryOne,
+					ResponseBody:   `[{"id":"ws-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workspaceConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workspaceUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusOK,
@@ -788,6 +795,11 @@ func TestUpdateWorkspaceCommand(t *testing.T) {
 				args: []string{"ws-cli-1"},
 			},
 			mockEndpoints: getBulkUpdateWorkspaceEndpoints(workspaceUpdateEndpointsConfig{
+				workspacesConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkspacesQueryOne,
+					ResponseBody:   `[{"id":"ws-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workspaceConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workspaceUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusUnauthorized,
@@ -829,6 +841,11 @@ func TestUpdateWorkspaceCommand(t *testing.T) {
 				args: []string{"ws-cli-1"},
 			},
 			mockEndpoints: getBulkUpdateWorkspaceEndpoints(workspaceUpdateEndpointsConfig{
+				workspacesConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkspacesQueryOne,
+					ResponseBody:   `[{"id":"ws-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workspaceConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workspaceUpdateRequestBodyIDsOnly,
 					ResponseStatus:      http.StatusOK,
@@ -848,6 +865,11 @@ func TestUpdateWorkspaceCommand(t *testing.T) {
 				args:        []string{"ws-cli-1"},
 			},
 			mockEndpoints: getSingleUpdateWorkspaceEndpoints(workspaceUpdateEndpointsConfig{
+				workspacesConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedResolveWorkspacesQueryOne,
+					ResponseBody:   `[{"id":"ws-cli-1"}]`,
+					ResponseStatus: http.StatusOK,
+				},
 				workspaceConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: workspaceUpdateRequestBodyNameDesc,
 					ResponseStatus:      http.StatusOK,
@@ -1045,6 +1067,7 @@ type workspaceEndpointsConfig struct {
 
 type workspaceUpdateEndpointsConfig struct {
 	initializerConfig stubs.StubHTTPPartialGetConfig
+	workspacesConfig  stubs.StubHTTPPartialGetConfig
 	workspaceConfig   stubs.StubHTTPPartialPostConfig
 }
 
@@ -1126,6 +1149,18 @@ func getUpdateWorkspaceEndpoints(
 ) []stubs.StubHTTPEndpoint {
 	endpoints := append(stubs.GetStubAuthEndpoints(),
 		getInitializerEndpoints(config.initializerConfig)...)
+	endpoints = append(endpoints, stubs.StubHTTPEndpoint{
+		Request: stubs.StubHTTPRequest{
+			Method:          http.MethodGet,
+			URL:             postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
+			ExpectedHeaders: stubs.StubAuthHeaders,
+			ExpectedQuery:   config.workspacesConfig.ExpectedQuery,
+		},
+		Response: stubs.StubHTTPResponse{
+			Status: config.workspacesConfig.ResponseStatus,
+			Body:   config.workspacesConfig.ResponseBody,
+		},
+	})
 	return append(endpoints, stubs.StubHTTPEndpoint{
 		Request: stubs.StubHTTPRequest{
 			Method:          http.MethodPost,
diff --git a/internal/cmd/delete/delete_test.go b/internal/cmd/delete/delete_test.go
index 0a10fd4..fda2d92 100644
--- a/internal/cmd/delete/delete_test.go
+++ b/internal/cmd/delete/delete_test.go
@@ -38,6 +38,24 @@ const (
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
 )
 
+const (
+	expectedImageResolveQuery      = `or=(controller_id.in.(image-1,image-2),name.in.(image-1,image-2))`
+	expectedJobResolveQuery        = `or=(name.in.(job-1,job-2))`
+	expectedSnapshotResolveQuery   = `or=(controller_id.in.(snapshot-1,snapshot-2),name.in.(snapshot-1,snapshot-2))`
+	expectedSubmissionResolveQuery = `or=(controller_id.in.(submission-1,submission-2),` +
+		`job_name.in.(submission-1,submission-2))`
+	expectedWorkbenchResolveQuery    = `or=(controller_id.in.(workbench-1,workbench-2),name.in.(workbench-1,workbench-2))`
+	expectedWorkspaceResolveQueryOne = `or=(controller_id.in.(workspace-1),name.in.(workspace-1))`
+	expectedWorkspaceResolveQueryTwo = `or=(controller_id.in.(workspace-1,workspace-2),name.in.(workspace-1,workspace-2))`
+	imageResolveResponseBody         = `[{"id":"image-1"},{"id":"image-2"}]`
+	jobResolveResponseBody           = `[{"id":"job-1"},{"id":"job-2"}]`
+	snapshotResolveResponseBody      = `[{"id":"snapshot-1"},{"id":"snapshot-2"}]`
+	submissionResolveResponseBody    = `[{"id":"submission-1"},{"id":"submission-2"}]`
+	workbenchResolveResponseBody     = `[{"id":"workbench-1"},{"id":"workbench-2"}]`
+	workspaceResolveResponseBodyOne  = `[{"id":"workspace-1"}]`
+	workspaceResolveResponseBodyTwo  = `[{"id":"workspace-1"},{"id":"workspace-2"}]`
+)
+
 var (
 	expectedImageDisplay          = strings.Join([]string{image1ID, image2ID}, "\n")
 	expectedJobDisplay            = strings.Join([]string{job1ID, job2ID}, "\n")
@@ -99,6 +117,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{workspace1ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedWorkspaceResolveQueryOne,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   workspaceResolveResponseBodyOne,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkspaceBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: `{"ids":["workspace-1"]}`,
@@ -121,6 +145,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{image1ID, image2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointImages),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedImageResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   imageResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCImageBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedImageRequestBody,
@@ -140,6 +170,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{image1ID, image2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointImages),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedImageResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   imageResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCImageBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedImageRequestBody,
@@ -159,6 +195,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{submission1ID, submission2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedSubmissionResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   submissionResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCJobRunBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedSubmissionRequestBody,
@@ -178,6 +220,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{submission1ID, submission2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedSubmissionResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   submissionResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCJobRunBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedSubmissionRequestBody,
@@ -197,6 +245,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{job1ID, job2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointJobs),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedJobResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   jobResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCJobBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedJobRequestBody,
@@ -216,6 +270,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{job1ID, job2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointJobs),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedJobResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   jobResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCJobBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedJobRequestBody,
@@ -235,6 +295,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{snapshot1ID, snapshot2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointSnapshots),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedSnapshotResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   snapshotResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCSnapshotBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedSnapshotRequestBody,
@@ -254,6 +320,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{snapshot1ID, snapshot2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointSnapshots),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedSnapshotResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   snapshotResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCSnapshotBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedSnapshotRequestBody,
@@ -273,6 +345,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{workbench1ID, workbench2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointWorkbenches),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedWorkbenchResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   workbenchResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkbenchBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedWorkbenchRequestBody,
@@ -292,6 +370,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{workbench1ID, workbench2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointWorkbenches),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedWorkbenchResolveQuery,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   workbenchResolveResponseBody,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkbenchBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedWorkbenchRequestBody,
@@ -311,6 +395,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{workspace1ID, workspace2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedWorkspaceResolveQueryTwo,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   workspaceResolveResponseBodyTwo,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkspaceBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedWorkspaceRequestBody,
@@ -330,6 +420,12 @@ func TestDeleteCommand(t *testing.T) {
 				resourceIDs:  []string{workspace1ID, workspace2ID},
 			},
 			mockEndpoints: getDeleteEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
+				stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  expectedWorkspaceResolveQueryTwo,
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   workspaceResolveResponseBodyTwo,
+				},
 				postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkspaceBulkDelete),
 				stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: expectedWorkspaceRequestBody,
@@ -432,10 +528,23 @@ func getDeleteArgs(
 }
 
 func getDeleteEndpoints(
+	resolveEndpoint string,
+	resolveConfig stubs.StubHTTPPartialGetConfig,
 	urlUnderTest string,
 	config ...stubs.StubHTTPPartialPostConfig,
 ) []stubs.StubHTTPEndpoint {
-	endpoints := stubs.GetStubAuthEndpoints()
+	endpoints := append(stubs.GetStubAuthEndpoints(), stubs.StubHTTPEndpoint{
+		Request: stubs.StubHTTPRequest{
+			Method:          http.MethodGet,
+			URL:             resolveEndpoint,
+			ExpectedHeaders: stubs.StubAuthHeaders,
+			ExpectedQuery:   resolveConfig.ExpectedQuery,
+		},
+		Response: stubs.StubHTTPResponse{
+			Status: resolveConfig.ResponseStatus,
+			Body:   resolveConfig.ResponseBody,
+		},
+	})
 	for _, cfg := range config {
 		endpoints = append(endpoints,
 			stubs.StubHTTPEndpoint{
diff --git a/internal/cmd/describe/command.go b/internal/cmd/describe/command.go
index 6e8fb7d..a905d5d 100644
--- a/internal/cmd/describe/command.go
+++ b/internal/cmd/describe/command.go
@@ -61,7 +61,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdDescribeTypeDescription,
+				i18n.CmdDescribeTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageType)},
 			),
 		},
@@ -75,7 +75,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageBuildType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdDescribeTypeDescription,
+				i18n.CmdDescribeTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageBuildType)},
 			),
 		},
@@ -89,7 +89,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdDescribeTypeDescription,
+				i18n.CmdDescribeTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobType)},
 			),
 		},
@@ -103,7 +103,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobSubmissionType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdDescribeTypeDescription,
+				i18n.CmdDescribeTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobSubmissionType)},
 			),
 		},
@@ -117,7 +117,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.SnapshotType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdDescribeTypeDescription,
+				i18n.CmdDescribeTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.SnapshotType)},
 			),
 		},
@@ -145,7 +145,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.WorkbenchType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdDescribeTypeDescription,
+				i18n.CmdDescribeTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.WorkbenchType)},
 			),
 		},
@@ -181,7 +181,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.VolumeType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdDescribeTypeDescription,
+				i18n.CmdDescribeTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.VolumeType)},
 			),
 		},
diff --git a/internal/cmd/describe/describe_test.go b/internal/cmd/describe/describe_test.go
index f9fb455..98cb88b 100644
--- a/internal/cmd/describe/describe_test.go
+++ b/internal/cmd/describe/describe_test.go
@@ -589,7 +589,7 @@ func TestDescribeCommand(t *testing.T) {
 			mockEndpoints: getDescribeEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds),
 				stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   describeGenericResourceBySingularTypeExpectedResponseBody,
 				},
@@ -608,7 +608,7 @@ func TestDescribeCommand(t *testing.T) {
 			mockEndpoints: getDescribeEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   describeGenericResourceBySingularTypeExpectedResponseBody,
 				},
@@ -627,7 +627,7 @@ func TestDescribeCommand(t *testing.T) {
 			mockEndpoints: getDescribeEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
 				stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),job_name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   describeGenericResourceBySingularTypeExpectedResponseBody,
 				},
@@ -646,7 +646,7 @@ func TestDescribeCommand(t *testing.T) {
 			mockEndpoints: getDescribeEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointJobs),
 				stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "id=in.(resource-1)",
+					ExpectedQuery:  "or=(name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   describeGenericResourceBySingularTypeExpectedResponseBody,
 				},
@@ -665,7 +665,7 @@ func TestDescribeCommand(t *testing.T) {
 			mockEndpoints: getDescribeEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointSnapshots),
 				stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   describeGenericResourceBySingularTypeExpectedResponseBody,
 				},
@@ -703,7 +703,7 @@ func TestDescribeCommand(t *testing.T) {
 			mockEndpoints: getDescribeEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointWorkbenches),
 				stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   describeGenericResourceBySingularTypeExpectedResponseBody,
 				},
@@ -722,7 +722,7 @@ func TestDescribeCommand(t *testing.T) {
 			mockEndpoints: getDescribeEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
 				stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   describeGenericResourceBySingularTypeExpectedResponseBody,
 				},
diff --git a/internal/cmd/edit/edit_test.go b/internal/cmd/edit/edit_test.go
index 220e455..8349f55 100644
--- a/internal/cmd/edit/edit_test.go
+++ b/internal/cmd/edit/edit_test.go
@@ -27,6 +27,7 @@ const (
 	testResourceID1 = "resource-1"
 	oneImageJSON    = `[
     {
+        "id": "resource-1",
         "image_name": "test-image",
         "image_registry": "example.com",
         "image_tag": "latest",
@@ -44,11 +45,20 @@ const (
         "image_name": "test-image",
         "image_registry": "example.com",
         "image_tag": "latest",
-        "name": "resource-2"
+        "name": "resource-1"
     }
 ]`
 	oneUpdatedImageJSON = `{"ids":["resource-1"],"param":{"image_name":"test-image",` +
 		`"image_registry":"example.com","image_tag":"latest","name":"UPDATED"}}`
+	oneUpdatedImageResponseJSON = `[
+    {
+        "id": "resource-1",
+        "image_name": "test-image",
+        "image_registry": "example.com",
+        "image_tag": "latest",
+        "name": "UPDATED"
+    }
+]`
 	oneWorkbenchJSON = `[
     {
         "id": "resource-1",
@@ -129,7 +139,7 @@ const (
         "workspaces": [
             {
                 "id": "sas-workbench-v0fq4c4ff9xqlv70j76amcqe",
-                "mountName": "replace-me"
+                "mountName": "UPDATED"
             }
         ]
     }`
@@ -145,9 +155,10 @@ const (
 		`"workspaces":[{"id":"sas-workbench-v0fq4c4ff9xqlv70j76amcqe","mountName":"UPDATED"}]` +
 		`}}`
 	oneJobJSON = `[{
+    "id": "resource-1",
     "application_id": "workbench-environment-builder",
     "dataplane_id": "ae0d1d17-c239-443e-ab80-70731b9e15de",
-    "name": "Job-2",
+    "name": "resource-1",
     "nodepool": "C0M0",
     "services": [
         {
@@ -178,17 +189,49 @@ const (
 	oneUpdatedJobJSON = `{"ids":["resource-1"],` +
 		`"param":{"application_id":"workbench-environment-builder",` +
 		`"dataplane_id":"ae0d1d17-c239-443e-ab80-70731b9e15de",` +
-		`"name":"Job-2",` +
+		`"name":"resource-1",` +
 		`"nodepool":"C0M0",` +
 		`"services":[{"id":"workbench-environment-builder",` +
 		`"env":{"DEFAULT_MOUNTNAME":"containers","WB_LOCALE":"en-US,en"}}],` +
 		`"spec":{"tasks":{"default":{"cmds":["echo \"test123\""]}},"version":"3"},` +
 		`"workspaces":[{"id":"sas-workbench-5tcc2a32w0wu293go4nu8ldvj","mountName":"UPDATED"}]}}`
+	oneUpdatedJobResponseJSON = `[{
+    "id": "resource-1",
+    "application_id": "workbench-environment-builder",
+    "dataplane_id": "ae0d1d17-c239-443e-ab80-70731b9e15de",
+    "name": "resource-1",
+    "nodepool": "C0M0",
+    "services": [
+        {
+            "id": "workbench-environment-builder",
+            "env": {
+                "DEFAULT_MOUNTNAME": "containers",
+                "WB_LOCALE": "en-US,en"
+            }
+        }
+    ],
+    "spec": {
+        "tasks": {
+            "default": {
+                "cmds": [
+                    "echo \"test123\""
+                ]
+            }
+        },
+        "version": "3"
+    },
+    "workspaces": [
+        {
+            "id": "sas-workbench-5tcc2a32w0wu293go4nu8ldvj",
+            "mountName": "UPDATED"
+        }
+    ]
+}]`
 	expectedInvalidEndpointErr = "failed to get device token" +
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
 	expectedInvalidResourceErr = "" +
-		"GET ${URL}/postgrest/jobs?id=in.%28resource-1%29 failed" +
+		"GET ${URL}/postgrest/jobs?or=%28name.in.%28resource-1%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* invalid character 'i' looking for beginning of value"
 	expectedEditErr = "" +
@@ -275,7 +318,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   "[]",
 				},
@@ -292,7 +335,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   twoImagesJSON,
 				},
@@ -315,7 +358,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -338,7 +381,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -362,7 +405,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -389,7 +432,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -410,7 +453,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -433,7 +476,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -456,7 +499,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -464,11 +507,11 @@ func TestEditCommand(t *testing.T) {
 				postConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: oneUpdatedImageJSON,
 					ResponseStatus:      http.StatusOK,
-					ResponseBody:        oneImageJSON,
+					ResponseBody:        oneUpdatedImageResponseJSON,
 				},
 			}),
 			expected: editExpected{
-				getCalls:  1,
+				getCalls:  2,
 				postCalls: 1,
 				displays:  []string{stubs.ExpectedDeviceAuthDisplay, "resource-1"},
 			},
@@ -483,7 +526,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -510,7 +553,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointImages),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneImageJSON,
 				},
@@ -531,7 +574,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointWorkbenches),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(resource-1)",
+					ExpectedQuery:  "or=(controller_id.in.(resource-1),name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneWorkbenchJSON,
 				},
@@ -543,7 +586,7 @@ func TestEditCommand(t *testing.T) {
 				},
 			}),
 			expected: editExpected{
-				getCalls:  1,
+				getCalls:  2,
 				postCalls: 1,
 				displays:  []string{stubs.ExpectedDeviceAuthDisplay, "resource-1"},
 			},
@@ -558,7 +601,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointJobs),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "id=in.(resource-1)",
+					ExpectedQuery:  "or=(name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneJobJSON,
 				},
@@ -566,11 +609,11 @@ func TestEditCommand(t *testing.T) {
 				postConfig: stubs.StubHTTPPartialPostConfig{
 					ExpectedRequestBody: oneUpdatedJobJSON,
 					ResponseStatus:      http.StatusOK,
-					ResponseBody:        oneJobJSON,
+					ResponseBody:        oneUpdatedJobResponseJSON,
 				},
 			}),
 			expected: editExpected{
-				getCalls:  1,
+				getCalls:  2,
 				postCalls: 1,
 				displays:  []string{stubs.ExpectedDeviceAuthDisplay, "resource-1"},
 			},
@@ -595,7 +638,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointJobs),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "id=in.(resource-1)",
+					ExpectedQuery:  "or=(name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   `[invalid`,
 				},
@@ -616,7 +659,7 @@ func TestEditCommand(t *testing.T) {
 			mockEndpoints: getEditEndpoints(editConfig{
 				getURL: postgrest.Endpoint(constants.WorkbenchEndpointJobs),
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "id=in.(resource-1)",
+					ExpectedQuery:  "or=(name.in.(resource-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   oneJobJSON,
 				},
@@ -629,7 +672,7 @@ func TestEditCommand(t *testing.T) {
 			}),
 			expected: editExpected{
 				displays:  []string{stubs.ExpectedDeviceAuthDisplay},
-				getCalls:  1,
+				getCalls:  2,
 				postCalls: 1,
 				err:       expectedEditErr,
 			},
@@ -734,20 +777,23 @@ type editConfig struct {
 func getEditEndpoints(
 	config editConfig,
 ) []stubs.StubHTTPEndpoint {
+	getEndpoint := stubs.StubHTTPEndpoint{
+		Request: stubs.StubHTTPRequest{
+			Method:          http.MethodGet,
+			URL:             config.getURL,
+			ExpectedHeaders: stubs.StubAuthHeaders,
+			ExpectedQuery:   config.getConfig.ExpectedQuery,
+		},
+		Response: stubs.StubHTTPResponse{
+			Status: config.getConfig.ResponseStatus,
+			Body:   config.getConfig.ResponseBody,
+		},
+	}
 	endpoints := stubs.GetStubAuthEndpoints()
+	endpoints = append(endpoints, getEndpoint)
+	// ID resolution repeats the GET before bulk updates.
+	endpoints = append(endpoints, getEndpoint)
 	endpoints = append(endpoints,
-		stubs.StubHTTPEndpoint{
-			Request: stubs.StubHTTPRequest{
-				Method:          http.MethodGet,
-				URL:             config.getURL,
-				ExpectedHeaders: stubs.StubAuthHeaders,
-				ExpectedQuery:   config.getConfig.ExpectedQuery,
-			},
-			Response: stubs.StubHTTPResponse{
-				Status: config.getConfig.ResponseStatus,
-				Body:   config.getConfig.ResponseBody,
-			},
-		},
 		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
diff --git a/internal/cmd/get/command.go b/internal/cmd/get/command.go
index 21c5817..2937278 100644
--- a/internal/cmd/get/command.go
+++ b/internal/cmd/get/command.go
@@ -90,7 +90,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdGetTypeDescription,
+				i18n.CmdGetTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageType)},
 			),
 		},
@@ -104,7 +104,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageBuildType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdGetTypeDescription,
+				i18n.CmdGetTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.ImageBuildType)},
 			),
 		},
@@ -132,7 +132,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdGetTypeDescription,
+				i18n.CmdGetTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobType)},
 			),
 		},
@@ -146,7 +146,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobSubmissionType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdGetTypeDescription,
+				i18n.CmdGetTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.JobSubmissionType)},
 			),
 		},
@@ -202,7 +202,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.SnapshotType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdGetTypeDescription,
+				i18n.CmdGetTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.SnapshotType)},
 			),
 		},
@@ -244,7 +244,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.WorkbenchType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdGetTypeDescription,
+				i18n.CmdGetTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.WorkbenchType)},
 			),
 		},
@@ -280,7 +280,7 @@ func getSubcommands(
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.VolumeType)},
 			),
 			Long: i18n.Format(
-				i18n.CmdGetTypeDescription,
+				i18n.CmdGetTypeDescriptionWithName,
 				i18n.A{constants.I18nArgTypeKey: types.Titler.String(constants.VolumeType)},
 			),
 		},
diff --git a/internal/cmd/log/log_test.go b/internal/cmd/log/log_test.go
index fe000f5..452fc1c 100644
--- a/internal/cmd/log/log_test.go
+++ b/internal/cmd/log/log_test.go
@@ -61,19 +61,20 @@ func TestLogCommand(t *testing.T) {
 			name: "new log function image build list non-200 from server",
 			input: logInput{
 				resourceType: constants.ImageBuildsType,
-				resourceIDs:  []string{"controller-1"},
+				resourceIDs:  []string{"image-build-1"},
 			},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(image-build-1),name.in.(image-build-1))",
 					ResponseStatus: http.StatusInternalServerError,
 					ResponseBody:   `resource-error`,
 				},
 			}),
 			expected: logExpected{
 				displays: []string{stubs.ExpectedDeviceAuthDisplay},
-				err: "GET ${URL}/postgrest/image_builds?controller_id=in.%28controller-1%29 failed\n" +
+				err: "GET ${URL}/postgrest/image_builds" +
+					"?or=%28controller_id.in.%28image-build-1%29%2Cname.in.%28image-build-1%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 500 resource-error",
 				resourceCalls: 1},
@@ -82,12 +83,12 @@ func TestLogCommand(t *testing.T) {
 			name: "new log function image build raw log non-200 from server",
 			input: logInput{
 				resourceType: constants.ImageBuildsType,
-				resourceIDs:  []string{"controller-1"},
+				resourceIDs:  []string{"image-build-1"},
 			},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(image-build-1),name.in.(image-build-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   logImageBuildsResponseBody,
 				},
@@ -108,19 +109,20 @@ func TestLogCommand(t *testing.T) {
 			name: "new log function job submission list non-200 from server",
 			input: logInput{
 				resourceType: constants.JobSubmissionsType,
-				resourceIDs:  []string{"controller-1"},
+				resourceIDs:  []string{"job-submission-1"},
 			},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(job-submission-1),job_name.in.(job-submission-1))",
 					ResponseStatus: http.StatusInternalServerError,
 					ResponseBody:   "resource-error",
 				},
 			}),
 			expected: logExpected{
 				displays: []string{stubs.ExpectedDeviceAuthDisplay},
-				err: "GET ${URL}/postgrest/job_runs?controller_id=in.%28controller-1%29 failed\n" +
+				err: "GET ${URL}/postgrest/job_runs" +
+					"?or=%28controller_id.in.%28job-submission-1%29%2Cjob_name.in.%28job-submission-1%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 500 resource-error",
 				resourceCalls: 1},
@@ -129,12 +131,12 @@ func TestLogCommand(t *testing.T) {
 			name: "new log function job submission raw log non-200 from server",
 			input: logInput{
 				resourceType: constants.JobSubmissionsType,
-				resourceIDs:  []string{"controller-1"},
+				resourceIDs:  []string{"job-submission-1"},
 			},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(job-submission-1),job_name.in.(job-submission-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   logJobSubmissionsResponseBody,
 				},
@@ -153,11 +155,11 @@ func TestLogCommand(t *testing.T) {
 		},
 		{
 			name:  "new log function prints image build logs by singular type",
-			input: logInput{resourceType: constants.ImageBuildType, resourceIDs: []string{"controller-1"}},
+			input: logInput{resourceType: constants.ImageBuildType, resourceIDs: []string{"image-build-1"}},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(image-build-1),name.in.(image-build-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   logImageBuildsResponseBody,
 				},
@@ -177,12 +179,12 @@ func TestLogCommand(t *testing.T) {
 			name: "new log function prints image build logs by plural type",
 			input: logInput{
 				resourceType: constants.ImageBuildsType,
-				resourceIDs:  []string{"controller-1"},
+				resourceIDs:  []string{"image-build-1"},
 			},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(image-build-1),name.in.(image-build-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   logImageBuildsResponseBody,
 				},
@@ -200,11 +202,11 @@ func TestLogCommand(t *testing.T) {
 		},
 		{
 			name:  "new log function prints job submission logs by singular type",
-			input: logInput{resourceType: constants.JobSubmissionType, resourceIDs: []string{"controller-2"}},
+			input: logInput{resourceType: constants.JobSubmissionType, resourceIDs: []string{"job-submission-1"}},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-2)",
+					ExpectedQuery:  "or=(controller_id.in.(job-submission-1),job_name.in.(job-submission-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   logJobSubmissionsResponseBody,
 				},
@@ -222,11 +224,11 @@ func TestLogCommand(t *testing.T) {
 		},
 		{
 			name:  "new log function prints job submission logs by plural type",
-			input: logInput{resourceType: constants.JobSubmissionsType, resourceIDs: []string{"controller-2"}},
+			input: logInput{resourceType: constants.JobSubmissionsType, resourceIDs: []string{"job-submission-1"}},
 			mockEndpoints: getLogEndpoints(logConfig{
 				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
 				getResourceConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-2)",
+					ExpectedQuery:  "or=(controller_id.in.(job-submission-1),job_name.in.(job-submission-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   logJobSubmissionsResponseBody,
 				},
@@ -253,6 +255,38 @@ func TestLogCommand(t *testing.T) {
 				err: expectedInvalidEndpointErr,
 			},
 		},
+		{
+			name:  "empty job submission log url",
+			input: logInput{resourceType: constants.JobSubmissionsType, resourceIDs: []string{"job-submission-2"}},
+			mockEndpoints: getLogEndpoints(logConfig{
+				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointJobRuns),
+				getResourceConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  "or=(controller_id.in.(job-submission-2),job_name.in.(job-submission-2))",
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   logJobSubmissionsResponseBody,
+				},
+			}),
+			expected: logExpected{
+				displays:      []string{stubs.ExpectedDeviceAuthDisplay},
+				resourceCalls: 1,
+			},
+		},
+		{
+			name:  "empty build log url",
+			input: logInput{resourceType: constants.ImageBuildType, resourceIDs: []string{"image-build-2"}},
+			mockEndpoints: getLogEndpoints(logConfig{
+				resourceURL: postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds),
+				getResourceConfig: stubs.StubHTTPPartialGetConfig{
+					ExpectedQuery:  "or=(controller_id.in.(image-build-2),name.in.(image-build-2))",
+					ResponseStatus: http.StatusOK,
+					ResponseBody:   logImageBuildsResponseBody,
+				},
+			}),
+			expected: logExpected{
+				displays:      []string{stubs.ExpectedDeviceAuthDisplay},
+				resourceCalls: 1,
+			},
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/cmd/rebuild/rebuild_test.go b/internal/cmd/rebuild/rebuild_test.go
index 169c4fb..33d20de 100644
--- a/internal/cmd/rebuild/rebuild_test.go
+++ b/internal/cmd/rebuild/rebuild_test.go
@@ -29,7 +29,8 @@ const (
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
 	expectedInvalidResourceErr = "" +
-		"GET ${URL}/postgrest/images?controller_id=in.%28controller-1%29 failed" +
+		"GET ${URL}/postgrest/images?" +
+		"or=%28controller_id.in.%28image-1%29%2Cname.in.%28image-1%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* invalid character 'i' looking for beginning of value"
 )
@@ -59,10 +60,10 @@ func TestRebuildCommand(t *testing.T) {
 	tests := []rebuildTestCase{
 		{
 			name:  "rebuild images by singular type",
-			input: rebuildInput{resourceType: constants.ImageType, resourceIDs: []string{"controller-1"}},
+			input: rebuildInput{resourceType: constants.ImageType, resourceIDs: []string{"image-1", "image-2"}},
 			mockEndpoints: getRebuildEndpoints(rebuildConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(image-1,image-2),name.in.(image-1,image-2))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   rebuildImagesResponseBody,
 				},
@@ -79,10 +80,10 @@ func TestRebuildCommand(t *testing.T) {
 		},
 		{
 			name:  "rebuild images by plural type",
-			input: rebuildInput{resourceType: constants.ImagesType, resourceIDs: []string{"controller-2"}},
+			input: rebuildInput{resourceType: constants.ImagesType, resourceIDs: []string{"image-1", "image-2"}},
 			mockEndpoints: getRebuildEndpoints(rebuildConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-2)",
+					ExpectedQuery:  "or=(controller_id.in.(image-1,image-2),name.in.(image-1,image-2))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   rebuildImagesResponseBody,
 				},
@@ -99,10 +100,10 @@ func TestRebuildCommand(t *testing.T) {
 		},
 		{
 			name:  "returns error when rebuild call fails",
-			input: rebuildInput{resourceType: constants.ImagesType, resourceIDs: []string{"controller-2"}},
+			input: rebuildInput{resourceType: constants.ImagesType, resourceIDs: []string{"image-1"}},
 			mockEndpoints: getRebuildEndpoints(rebuildConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-2)",
+					ExpectedQuery:  "or=(controller_id.in.(image-1),name.in.(image-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   rebuildImagesResponseBody,
 				},
@@ -122,7 +123,7 @@ func TestRebuildCommand(t *testing.T) {
 			input: rebuildInput{
 				endpoint:     "a\nb",
 				resourceType: constants.ImageType,
-				resourceIDs:  []string{"controller-1"},
+				resourceIDs:  []string{"image-1"},
 			},
 			expected: rebuildExpected{
 				err: expectedInvalidEndpointErr,
@@ -132,11 +133,11 @@ func TestRebuildCommand(t *testing.T) {
 			name: "invalid image response",
 			input: rebuildInput{
 				resourceType: constants.ImageType,
-				resourceIDs:  []string{"controller-1"},
+				resourceIDs:  []string{"image-1"},
 			},
 			mockEndpoints: getRebuildEndpoints(rebuildConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(image-1),name.in.(image-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   `[invalid`,
 				},
diff --git a/internal/cmd/state/state.go b/internal/cmd/state/state.go
index 53d65a1..a82f140 100644
--- a/internal/cmd/state/state.go
+++ b/internal/cmd/state/state.go
@@ -105,12 +105,7 @@ func setWorkbenchDesiredState(
 	desiredState string,
 	svc *workbench.WorkbenchService,
 ) error {
-	workbenches, err := workbench.GetWorkbenches(ctx, svc, ids, "")
-	if err != nil {
-		return err
-	}
-	resolvedIDs := getIDs(workbenches)
-	changedIDs, err := bulkUpdateState(ctx, svc, desiredState, resolvedIDs)
+	changedIDs, err := bulkUpdateState(ctx, svc, desiredState, ids)
 	if err != nil {
 		return err
 	}
diff --git a/internal/cmd/state/state_test.go b/internal/cmd/state/state_test.go
index 149983e..1018c1e 100644
--- a/internal/cmd/state/state_test.go
+++ b/internal/cmd/state/state_test.go
@@ -51,7 +51,8 @@ const (
 		"\n Caused by:" +
 		"\n\t* parse \"a\\nb\": net/url: invalid control character in URL"
 	expectedInvalidResourceErr = "" +
-		"GET ${URL}/postgrest/workbenches?controller_id=in.%28controller-1%29 failed" +
+		"GET ${URL}/postgrest/workbenches?" +
+		"or=%28controller_id.in.%28controller-1%29%2Cname.in.%28controller-1%29%29 failed" +
 		"\n Caused by:" +
 		"\n\t* invalid character 'i' looking for beginning of value"
 	expectedInvalidUpdateResponseErr = "" +
@@ -112,7 +113,7 @@ func TestStateCommands(t *testing.T) {
 			},
 			mockEndpoints: getStateEndpoints(stateConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1,controller-2)",
+					ExpectedQuery:  "or=(controller_id.in.(controller-1,controller-2),name.in.(controller-1,controller-2))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   stateGetResponseBodySingular,
 				},
@@ -136,7 +137,7 @@ func TestStateCommands(t *testing.T) {
 			},
 			mockEndpoints: getStateEndpoints(stateConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-3)",
+					ExpectedQuery:  "or=(controller_id.in.(controller-3),name.in.(controller-3))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   stateGetResponseBodyPlural,
 				},
@@ -171,7 +172,7 @@ func TestStateCommands(t *testing.T) {
 			},
 			mockEndpoints: getStateEndpoints(stateConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-1)",
+					ExpectedQuery:  "or=(controller_id.in.(controller-1),name.in.(controller-1))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   `[invalid`,
 				},
@@ -190,7 +191,7 @@ func TestStateCommands(t *testing.T) {
 			},
 			mockEndpoints: getStateEndpoints(stateConfig{
 				getConfig: stubs.StubHTTPPartialGetConfig{
-					ExpectedQuery:  "controller_id=in.(controller-3)",
+					ExpectedQuery:  "or=(controller_id.in.(controller-3),name.in.(controller-3))",
 					ResponseStatus: http.StatusOK,
 					ResponseBody:   stateGetResponseBodyPlural,
 				},
diff --git a/internal/i18n/i18n_messages_workbench_cli.go b/internal/i18n/i18n_messages_workbench_cli.go
index 6f80a4b..f67256d 100644
--- a/internal/i18n/i18n_messages_workbench_cli.go
+++ b/internal/i18n/i18n_messages_workbench_cli.go
@@ -15,7 +15,7 @@ var wbctlMessages = "\n" +
 	"wbctl-cli-icu.client.id.must.be.specified.msg=--client-id must be specified\n" +
 	"wbctl-cli-icu.cmd.api.usage.msg=Directly call Workbench API endpoints\n" +
 	"wbctl-cli-icu.cmd.cancel.desc=Cancels one or many Workbench resources\n" +
-	"wbctl-cli-icu.cmd.cancel.type.description.msg=Cancels the {type} resources identified by a list of resource ids. At least one id is required.\n" +
+	"wbctl-cli-icu.cmd.cancel.type.description.msg=Cancels the {type} resources identified by a list of resource ids or names. At least one id or name is required.\n" +
 	"wbctl-cli-icu.cmd.cancel.type.usage.msg=Cancel one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.cancel.usage.msg=Cancel running Workbench resources\n" +
 	"wbctl-cli-icu.cmd.create.desc=Creates Workbench resources.\n" +
@@ -23,23 +23,25 @@ var wbctlMessages = "\n" +
 	"wbctl-cli-icu.cmd.create.type.usage.msg=Create a new {type} resource\n" +
 	"wbctl-cli-icu.cmd.create.usage.msg=Create Workbench resources\n" +
 	"wbctl-cli-icu.cmd.delete.desc=Deletes one or many Workbench resources.\n" +
-	"wbctl-cli-icu.cmd.delete.type.description.msg=Deletes the {type} resources identified by a list of resource ids. At least one id is required.\n" +
+	"wbctl-cli-icu.cmd.delete.type.description.msg=Deletes the {type} resources identified by a list of resource ids or names. At least one id or name is required.\n" +
 	"wbctl-cli-icu.cmd.delete.type.usage.msg=Delete one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.delete.usage.msg=Delete Workbench resources\n" +
 	"wbctl-cli-icu.cmd.describe.desc=Print a detailed description of one or many Workbench resources.\n" +
 	"wbctl-cli-icu.cmd.describe.type.description.msg=Print detailed information about the {type} resources identified by an optional list of resource ids. All resources printed if no ids provided.\n" +
+	"wbctl-cli-icu.cmd.describe.type.description.with.name.msg=Print detailed information about the {type} resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.\n" +
 	"wbctl-cli-icu.cmd.describe.type.usage.msg=Describe one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.describe.usage.msg=Describe Workbench resources in detail\n" +
 	"wbctl-cli-icu.cmd.edit.description.msg=Edit a Workbench resource using the default editor.\n" +
-	"wbctl-cli-icu.cmd.edit.type.description.msg=Edit one {type} resource identified by id. One and only one id is required.\n" +
+	"wbctl-cli-icu.cmd.edit.type.description.msg=Edit one {type} resource identified by id or name. One and only one id or name is required.\n" +
 	"wbctl-cli-icu.cmd.edit.type.usage.msg=Edit a {type} resource\n" +
 	"wbctl-cli-icu.cmd.edit.usage.msg=Edit Workbench resources\n" +
 	"wbctl-cli-icu.cmd.get.desc=Print a table of basic information about one or many Workbench resources.\n" +
 	"wbctl-cli-icu.cmd.get.type.description.msg=Print a table of basic information about the {type} resources identified by an optional list of resource ids. All resources printed if no ids provided.\n" +
+	"wbctl-cli-icu.cmd.get.type.description.with.name.msg=Print a table of basic information about the {type} resources identified by an optional list of resource ids or names. All resources printed if no ids or names provided.\n" +
 	"wbctl-cli-icu.cmd.get.type.usage.msg=Get one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.get.usage.msg=Print Workbench resources\n" +
 	"wbctl-cli-icu.cmd.log.desc=Fetches the logs of one or many Workbench resources\n" +
-	"wbctl-cli-icu.cmd.log.type.description.msg=Print the log of the {type} resources identified a list of resource ids. At least one id is required.\n" +
+	"wbctl-cli-icu.cmd.log.type.description.msg=Print the log of the {type} resources identified by a list of resource ids or names. At least one id or name is required.\n" +
 	"wbctl-cli-icu.cmd.log.type.usage.msg=Print the log of one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.log.usage.msg=Fetch the logs of Workbench resources\n" +
 	"wbctl-cli-icu.cmd.login.desc=Authenticates with the Workbench API and stores the resulting token in ~/.wbctl/token.json so that subsequent commands do not require repeated authorization.\n" +
@@ -55,21 +57,21 @@ var wbctlMessages = "\n" +
 	"wbctl-cli-icu.cmd.profile.show.usage.msg=Show current profile\n" +
 	"wbctl-cli-icu.cmd.profile.usage.msg=Manage profiles\n" +
 	"wbctl-cli-icu.cmd.rebuild.desc=Rebuilds one or many Workbench resources\n" +
-	"wbctl-cli-icu.cmd.rebuild.type.description.msg=Rebuild the {type} resources identified by a list of resource ids. At least one id is required.\n" +
+	"wbctl-cli-icu.cmd.rebuild.type.description.msg=Rebuild the {type} resources identified by a list of resource ids or names. At least one id or name is required.\n" +
 	"wbctl-cli-icu.cmd.rebuild.type.usage.msg=Rebuild one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.rebuild.usage.msg=Rebuild Workbench image\n" +
 	"wbctl-cli-icu.cmd.settings.description.msg=Displays or sets user settings.\n" +
 	"wbctl-cli-icu.cmd.settings.usage.msg=Manage user settings\n" +
 	"wbctl-cli-icu.cmd.start.desc=Starts one or many Workbench resources\n" +
-	"wbctl-cli-icu.cmd.start.type.description.msg=Start the {type} resources identified by a list of resource ids. At least one id is required.\n" +
+	"wbctl-cli-icu.cmd.start.type.description.msg=Start the {type} resources identified by a list of resource ids or names. At least one id or name is required.\n" +
 	"wbctl-cli-icu.cmd.start.type.usage.msg=Start one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.start.usage.msg=Start stopped Workbench resources\n" +
 	"wbctl-cli-icu.cmd.stop.desc=Stops one or many Workbench resources\n" +
-	"wbctl-cli-icu.cmd.stop.type.description.msg=Stop the {type} resources identified by a list of resource ids. At least one id is required.\n" +
+	"wbctl-cli-icu.cmd.stop.type.description.msg=Stop the {type} resources identified by a list of resource ids or names. At least one id or name is required.\n" +
 	"wbctl-cli-icu.cmd.stop.type.usage.msg=Stop one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.stop.usage.msg=Stop running Workbench resources\n" +
 	"wbctl-cli-icu.cmd.update.desc=Updates one or many Workbench resources.\n" +
-	"wbctl-cli-icu.cmd.update.type.description.msg=Updates all the {type} resources identified by a list of ids with the options provided.\n" +
+	"wbctl-cli-icu.cmd.update.type.description.msg=Updates all the {type} resources identified by a list of ids or names with the options provided.\n" +
 	"wbctl-cli-icu.cmd.update.type.usage.msg=Update one or many {type} resources\n" +
 	"wbctl-cli-icu.cmd.update.usage.msg=Update Workbench resources\n" +
 	"wbctl-cli-icu.cmd.version.description.msg=Print version information of the wbctl binary.\n" +
diff --git a/internal/i18n/logmsg.go b/internal/i18n/logmsg.go
index cd62a74..dc53005 100644
--- a/internal/i18n/logmsg.go
+++ b/internal/i18n/logmsg.go
@@ -40,6 +40,7 @@ const (
 	CmdDeleteUsage                       = BundleID + ".cmd.delete.usage.msg"
 	CmdDescribeDescription               = BundleID + ".cmd.describe.desc"
 	CmdDescribeTypeDescription           = BundleID + ".cmd.describe.type.description.msg"
+	CmdDescribeTypeDescriptionWithName   = BundleID + ".cmd.describe.type.description.with.name.msg"
 	CmdDescribeTypeUsage                 = BundleID + ".cmd.describe.type.usage.msg"
 	CmdDescribeUsage                     = BundleID + ".cmd.describe.usage.msg"
 	CmdEditDescription                   = BundleID + ".cmd.edit.description.msg"
@@ -48,6 +49,7 @@ const (
 	CmdEditUsage                         = BundleID + ".cmd.edit.usage.msg"
 	CmdGetDescription                    = BundleID + ".cmd.get.desc"
 	CmdGetTypeDescription                = BundleID + ".cmd.get.type.description.msg"
+	CmdGetTypeDescriptionWithName        = BundleID + ".cmd.get.type.description.with.name.msg"
 	CmdGetTypeUsage                      = BundleID + ".cmd.get.type.usage.msg"
 	CmdGetUsage                          = BundleID + ".cmd.get.usage.msg"
 	CmdLogDescription                    = BundleID + ".cmd.log.desc"
diff --git a/internal/services/workbench/image_builds.go b/internal/services/workbench/image_builds.go
index bc3447d..4585b23 100644
--- a/internal/services/workbench/image_builds.go
+++ b/internal/services/workbench/image_builds.go
@@ -19,21 +19,28 @@ import (
 )
 
 // GetImageBuilds gets the image builds in the given WorkbenchService. If given
-// any ids, only the matching resources will be returned. Otherwise, all
+// any ids/names, only the matching resources will be returned. Otherwise, all
 // resources are returned.
 func GetImageBuilds(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	sortSpec string,
 ) (types.ImageBuilds, error) {
-	logutil.GetLogger().Info("getting image-builds", "ids", ids, "sort", sortSpec)
+	logutil.GetLogger().Info("getting image-builds", "args", args, "sort", sortSpec)
 	sortSpec = normalizeSortSpec(sortSpec, map[string][]string{
 		constants.SortKeyAge: {constants.SortKeyCreatedAt},
 	})
-	return result.Split(Get[types.ImageBuilds](
-		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds), IDsOrControllerIDsQuery(ids, sortSpec),
+	imageBuilds, err := result.Split(Get[types.ImageBuilds](
+		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointImageBuilds), IDsControllerIDsOrNamesQuery(args, sortSpec),
 	))
+	if err != nil {
+		return nil, err
+	}
+	return selectByIDThenName(
+		args,
+		imageBuilds,
+	), nil
 }
 
 // PrintImageBuilds returns a function that prints the given image builds.
diff --git a/internal/services/workbench/image_builds_test.go b/internal/services/workbench/image_builds_test.go
index ac3ed11..1dd3888 100644
--- a/internal/services/workbench/image_builds_test.go
+++ b/internal/services/workbench/image_builds_test.go
@@ -6,7 +6,6 @@ package workbench_test
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"strings"
 	"testing"
@@ -51,6 +50,13 @@ const (
     "controller_id": "controller-build-2",
     "created_by": "carol",
     "image_id": "img-controller"
+  },
+  {
+    "id": "ib-name",
+    "name": "build-name",
+    "controller_id": "ctrl-name",
+    "created_by": "ted",
+    "image_id": "img-name"
   }
 ]
 `
@@ -211,6 +217,13 @@ var (
 			CreatedBy:    "carol",
 			ImageID:      "img-controller",
 		},
+		{
+			ID:           "ib-name",
+			Name:         "build-name",
+			ControllerID: "ctrl-name",
+			CreatedBy:    "ted",
+			ImageID:      "img-name",
+		},
 	}
 )
 
@@ -225,13 +238,15 @@ func TestGetImageBuilds(t *testing.T) {
 			},
 		},
 		{
-			name: "get image builds success with mixed uuid and controller ids query",
+			name: "get image builds success with mixed uuid, controller id, names query",
 			request: getRequest{
-				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-build-2"},
+				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-build-2", "build-name"},
 				sort: "id.asc",
 			},
 			mockEndpoints: getImageBuildsGetEndpoints(
-				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),controller_id.in.(controller-build-2))&order=id.asc",
+				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),"+
+					"controller_id.in.(controller-build-2,build-name),"+
+					"name.in.(123e4567-e89b-12d3-a456-426614174000,controller-build-2,build-name))&order=id.asc",
 				http.StatusOK,
 				imageBuildsGetMixedIDsResponseJSON,
 			),
@@ -298,9 +313,9 @@ func TestGetImageBuilds(t *testing.T) {
 			request: getRequest{
 				ids: []string{"id-a"},
 			},
-			mockEndpoints: getImageBuildsGetEndpoints("controller_id=in.(id-a)", http.StatusOK, `[`),
+			mockEndpoints: getImageBuildsGetEndpoints("or=(controller_id.in.(id-a),name.in.(id-a))", http.StatusOK, `[`),
 			expected: getExpected[types.ImageBuilds]{
-				err: "GET %s/postgrest/image_builds?controller_id=in.%%28id-a%%29 failed\n" +
+				err: "GET ${URL}/postgrest/image_builds?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* unexpected EOF",
 			},
@@ -310,9 +325,13 @@ func TestGetImageBuilds(t *testing.T) {
 			request: getRequest{
 				ids: []string{"id-a"},
 			},
-			mockEndpoints: getImageBuildsGetEndpoints("controller_id=in.(id-a)", http.StatusUnauthorized, `unauthorized`),
+			mockEndpoints: getImageBuildsGetEndpoints(
+				"or=(controller_id.in.(id-a),name.in.(id-a))",
+				http.StatusUnauthorized,
+				`unauthorized`,
+			),
 			expected: getExpected[types.ImageBuilds]{
-				err: "GET %s/postgrest/image_builds?controller_id=in.%%28id-a%%29 failed\n" +
+				err: "GET ${URL}/postgrest/image_builds?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
@@ -327,7 +346,7 @@ func TestGetImageBuilds(t *testing.T) {
 
 			actual, err := workbench.GetImageBuilds(context.Background(), svc, tt.request.ids, tt.request.sort)
 			if tt.expected.err != "" {
-				require.EqualError(t, err, fmt.Sprintf(tt.expected.err, srv.URL))
+				require.EqualError(t, err, strings.ReplaceAll(tt.expected.err, "${URL}", srv.URL))
 				return
 			}
 
diff --git a/internal/services/workbench/images.go b/internal/services/workbench/images.go
index bee5de1..bf10ffc 100644
--- a/internal/services/workbench/images.go
+++ b/internal/services/workbench/images.go
@@ -19,16 +19,16 @@ import (
 	"github.com/sassoftware/wbctl/internal/ui"
 )
 
-// GetImages gets the images in the given WorkbenchService. If given any ids,
-// only the matching resources will be returned. Otherwise, all resources are
-// returned.
+// GetImages gets the images in the given WorkbenchService. If given any
+// ids/names, only the matching resources will be returned. Otherwise, all
+// resources are returned.
 func GetImages(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	sortSpec string,
 ) (types.Images, error) {
-	logutil.GetLogger().Info("getting images", "ids", ids, "sort", sortSpec)
+	logutil.GetLogger().Info("getting images", "args", args, "sort", sortSpec)
 	sortSpec = normalizeSortSpec(sortSpec, map[string][]string{
 		constants.SortKeyAge:          {constants.SortKeyCreatedAt},
 		constants.SortKeyImageBuildID: {constants.SortKeyLatestImageBuildID},
@@ -39,14 +39,31 @@ func GetImages(
 			constants.SortKeyImageTag,
 		},
 	})
-	return result.Split(Get[types.Images](
-		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointImages), IDsOrControllerIDsQuery(ids, sortSpec),
+	images, err := result.Split(Get[types.Images](
+		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointImages), IDsControllerIDsOrNamesQuery(args, sortSpec),
 	))
+	if err != nil {
+		return nil, err
+	}
+	return selectByIDThenName(
+		args,
+		images,
+	), nil
+}
+
+func resolveImageIDs(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	return resolveIDs(
+		ctx, s, args, GetImages, types.Image.GetID, constants.ImageType,
+	)
 }
 
-// BulkDeleteImages deletes the images with the given ids.
-func BulkDeleteImages(ctx context.Context, s *WorkbenchService, ids []string) ([]string, error) {
-	logutil.GetLogger().Info("deleting images", "ids", ids)
+// BulkDeleteImages deletes the images with the given ids/names.
+func BulkDeleteImages(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	logutil.GetLogger().Info("deleting images", "args", args)
+	ids, err := resolveImageIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[[]string](
 		ctx,
 		s,
@@ -57,7 +74,7 @@ func BulkDeleteImages(ctx context.Context, s *WorkbenchService, ids []string) ([
 
 // CreateImage creates an image.
 func CreateImage(ctx context.Context, s *WorkbenchService, body types.Image) (types.Image, error) {
-	logutil.GetLogger().Info("deleting images", "body", body)
+	logutil.GetLogger().Info("creating image", "body", body)
 	return result.Split(Post[types.Image](
 		ctx,
 		s,
@@ -75,14 +92,18 @@ func RebuildImage(ctx context.Context, s *WorkbenchService, id string) (bool, er
 }
 
 // BulkUpdateImages applies the given types.Image to all the images with the
-// given ids.
+// given ids/names.
 func BulkUpdateImages(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	body types.Image,
 ) (types.Images, error) {
-	logutil.GetLogger().Info("updating images", "ids", ids, "body", body)
+	logutil.GetLogger().Info("updating images", "args", args, "body", body)
+	ids, err := resolveImageIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[types.Images](
 		ctx,
 		s,
diff --git a/internal/services/workbench/images_test.go b/internal/services/workbench/images_test.go
index d82184e..d287b08 100644
--- a/internal/services/workbench/images_test.go
+++ b/internal/services/workbench/images_test.go
@@ -13,6 +13,7 @@ import (
 	"time"
 
 	"github.com/sassoftware/wbctl/internal/constants"
+	"github.com/sassoftware/wbctl/internal/i18n"
 	"github.com/sassoftware/wbctl/internal/postgrest"
 	"github.com/sassoftware/wbctl/internal/services/workbench"
 	"github.com/sassoftware/wbctl/internal/stubs"
@@ -36,7 +37,7 @@ const (
 ]
 `
 
-	imagesGetTwoResponseJSON = `
+	imagesGetMixedResponseJSON = `
 [
   {
     "id": "123e4567-e89b-12d3-a456-426614174000",
@@ -51,6 +52,13 @@ const (
     "image_name": "app-controller",
     "image_registry": "registry.controller.com",
     "controller_id": "controller-app-2"
+  },
+  {
+    "id": "img-name",
+    "name": "image-name",
+    "image_name": "image-name",
+    "image_registry": "registry.name.com",
+    "controller_id": "ctrl-name"
   }
 ]
 `
@@ -241,8 +249,6 @@ const (
 ]
 `
 
-	imagesDeleteSuccessResponseJSON = `["img-1", "img-2"]`
-
 	expectedImagesEmptyTableNonAdmin = "" +
 		"ID\tCONTROLLER_ID\tNAME\tDESCRIPTION\tIMAGE\tIMAGE_BUILD_ID\tSTATE\tMESSAGE\tAGE"
 
@@ -347,6 +353,9 @@ Pod pod-1:
   }
 ]
 `
+	imagesResolveIDsQuery              = "or=(controller_id.in.(img-1,img-2),name.in.(img-1,img-2))"
+	imagesResolveIDsResponseJSON       = `[{"id":"img-1"},{"id":"img-2"}]`
+	imagesDeleteSuccessResponseJSON    = `["img-1", "img-2"]`
 	expectedDeleteImageRequestJSON     = `{"ids":["img-1","img-2"]}`
 	expectedCreateImageRequestJSON     = `{"param":{"name":"image-1"}}`
 	createImageOneResponseBodyJSON     = `{"id":"img-1","name":"image-1"}`
@@ -365,7 +374,7 @@ var (
 		},
 	}
 
-	expectedResponseTwoImages = types.Images{
+	expectedResponseMixedImages = types.Images{
 		{
 			ID:            "123e4567-e89b-12d3-a456-426614174000",
 			Name:          "app-uuid",
@@ -380,6 +389,13 @@ var (
 			ImageRegistry: "registry.controller.com",
 			ControllerID:  "controller-app-2",
 		},
+		{
+			ID:            "img-name",
+			Name:          "image-name",
+			ImageName:     "image-name",
+			ImageRegistry: "registry.name.com",
+			ControllerID:  "ctrl-name",
+		},
 	}
 )
 
@@ -391,6 +407,8 @@ func TestDeleteImage(t *testing.T) {
 				ids: []string{"img-1", "img-2"},
 			},
 			mockEndpoints: getImagesDeleteEndpoints(
+				imagesResolveIDsQuery,
+				imagesResolveIDsResponseJSON,
 				expectedDeleteImageRequestJSON,
 				http.StatusOK,
 				imagesDeleteSuccessResponseJSON,
@@ -405,6 +423,8 @@ func TestDeleteImage(t *testing.T) {
 				ids: []string{"img-1", "img-2"},
 			},
 			mockEndpoints: getImagesDeleteEndpoints(
+				imagesResolveIDsQuery,
+				imagesResolveIDsResponseJSON,
 				expectedDeleteImageRequestJSON,
 				http.StatusUnauthorized,
 				`unauthorized`,
@@ -415,6 +435,25 @@ func TestDeleteImage(t *testing.T) {
 					"\t* 401 unauthorized",
 			},
 		},
+		{
+			name: "delete image not found",
+			request: deleteRequest{
+				ids: []string{"missing"},
+			},
+			mockEndpoints: getImagesDeleteEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: deleteExpected{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.ImageType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -540,6 +579,23 @@ func TestRebuildImage(t *testing.T) {
 
 func TestBulkUpdateImages(t *testing.T) {
 	tests := []bulkupdateTestCase[types.Image, types.Images]{
+		{
+			name:    "bulk update images not found",
+			request: bulkupdateRequest[types.Image]{ids: []string{"missing"}, body: types.Image{Name: "updated-name"}},
+			mockEndpoints: getImagesBulkUpdateEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: bulkupdateExpected[types.Images]{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.ImageType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 		{
 			name: "bulk update images success",
 			request: bulkupdateRequest[types.Image]{
@@ -547,6 +603,8 @@ func TestBulkUpdateImages(t *testing.T) {
 				body: types.Image{Name: "updated-name"},
 			},
 			mockEndpoints: getImagesBulkUpdateEndpoints(
+				imagesResolveIDsQuery,
+				imagesResolveIDsResponseJSON,
 				expectedBulkUpdateImageRequestJSON,
 				http.StatusOK,
 				imagesBulkUpdateResponseJSON,
@@ -561,6 +619,8 @@ func TestBulkUpdateImages(t *testing.T) {
 			name:    "bulk update images non-200 from server",
 			request: bulkupdateRequest[types.Image]{ids: []string{"img-1", "img-2"}, body: types.Image{Name: "updated-name"}},
 			mockEndpoints: getImagesBulkUpdateEndpoints(
+				imagesResolveIDsQuery,
+				imagesResolveIDsResponseJSON,
 				expectedBulkUpdateImageRequestJSON,
 				http.StatusUnauthorized,
 				`unauthorized`,
@@ -575,6 +635,8 @@ func TestBulkUpdateImages(t *testing.T) {
 			name:    "bulk update images invalid json response",
 			request: bulkupdateRequest[types.Image]{ids: []string{"img-1", "img-2"}, body: types.Image{Name: "updated-name"}},
 			mockEndpoints: getImagesBulkUpdateEndpoints(
+				imagesResolveIDsQuery,
+				imagesResolveIDsResponseJSON,
 				expectedBulkUpdateImageRequestJSON,
 				http.StatusOK,
 				`[`,
@@ -664,18 +726,20 @@ func TestGetImages(t *testing.T) {
 			expected:      getExpected[types.Images]{result: expectedResponseOneImage},
 		},
 		{
-			name: "get images success with mixed uuid and controller ids query",
+			name: "get images success with mixed uuid, controller id, names query",
 			request: getRequest{
-				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-app-2"},
+				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-app-2", "image-name"},
 				sort: "id.asc",
 			},
 			mockEndpoints: getImagesGetEndpoints(
-				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),controller_id.in.(controller-app-2))&order=id.asc",
+				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),"+
+					"controller_id.in.(controller-app-2,image-name),"+
+					"name.in.(123e4567-e89b-12d3-a456-426614174000,controller-app-2,image-name))&order=id.asc",
 				http.StatusOK,
-				imagesGetTwoResponseJSON,
+				imagesGetMixedResponseJSON,
 			),
 			expected: getExpected[types.Images]{
-				result: expectedResponseTwoImages,
+				result: expectedResponseMixedImages,
 			},
 		},
 		{
@@ -755,18 +819,20 @@ func TestGetImages(t *testing.T) {
 		{
 			name:          "get images invalid json response",
 			request:       getRequest{ids: []string{"id-a"}},
-			mockEndpoints: getImagesGetEndpoints("controller_id=in.(id-a)", http.StatusOK, "["),
+			mockEndpoints: getImagesGetEndpoints("or=(controller_id.in.(id-a),name.in.(id-a))", http.StatusOK, "["),
 			expected: getExpected[types.Images]{
-				err: "GET ${URL}/postgrest/images?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/images?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* unexpected EOF"},
 		},
 		{
-			name:          "get images non-200 from server",
-			request:       getRequest{ids: []string{"id-a"}},
-			mockEndpoints: getImagesGetEndpoints("controller_id=in.(id-a)", http.StatusUnauthorized, "unauthorized"),
+			name:    "get images non-200 from server",
+			request: getRequest{ids: []string{"id-a"}},
+			mockEndpoints: getImagesGetEndpoints(
+				"or=(controller_id.in.(id-a),name.in.(id-a))", http.StatusUnauthorized, "unauthorized",
+			),
 			expected: getExpected[types.Images]{
-				err: "GET ${URL}/postgrest/images?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/images?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized"},
 		},
@@ -886,12 +952,14 @@ func createTokenWithRole(role string) string {
 }
 
 func getImagesDeleteEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getImagesGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCImageBulkDelete),
@@ -902,8 +970,7 @@ func getImagesDeleteEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getImagesCreateEndpoints(
@@ -928,12 +995,14 @@ func getImagesCreateEndpoints(
 }
 
 func getImagesBulkUpdateEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getImagesGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCImageBulkUpdate),
@@ -944,8 +1013,7 @@ func getImagesBulkUpdateEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getImagesGetEndpoints(
diff --git a/internal/services/workbench/job_submissions.go b/internal/services/workbench/job_submissions.go
index 5d026a1..bcb661d 100644
--- a/internal/services/workbench/job_submissions.go
+++ b/internal/services/workbench/job_submissions.go
@@ -21,27 +21,45 @@ import (
 )
 
 // GetJobSubmissions  gets the job submissions in the given WorkbenchService. If
-// given any ids, only the matching resources will be returned. Otherwise, all
+// given any ids/names, only the matching resources will be returned. Otherwise, all
 // resources are returned.
+// nolint:dupl
 func GetJobSubmissions(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	sortSpec string,
 ) (types.JobSubmissions, error) {
-	logutil.GetLogger().Info("getting job-submissions", "ids", ids, "sort", sortSpec)
+	logutil.GetLogger().Info("getting job-submissions", "args", args, "sort", sortSpec)
 	sortSpec = normalizeSortSpec(sortSpec, map[string][]string{
 		constants.SortKeyAge:   {constants.SortKeyCreatedAt},
 		constants.SortKeyState: {constants.SortKeyCurrentState},
 	})
-	return result.Split(Get[types.JobSubmissions](
-		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointJobRuns), IDsOrControllerIDsQuery(ids, sortSpec),
+	jobSubmissions, err := result.Split(Get[types.JobSubmissions](
+		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointJobRuns), IDsControllerIDsOrJobNamesQuery(args, sortSpec),
 	))
+	if err != nil {
+		return nil, err
+	}
+	return selectByIDThenName(
+		args,
+		jobSubmissions,
+	), nil
+}
+
+func resolveJobSubmissionIDs(ctx context.Context, s *WorkbenchService, ids []string) ([]string, error) {
+	return resolveIDs(
+		ctx, s, ids, GetJobSubmissions, types.JobSubmission.GetID, constants.JobSubmissionType,
+	)
 }
 
-// BulkDeleteJobSubmissions deletes the job submission with the given id.
-func BulkDeleteJobSubmissions(ctx context.Context, s *WorkbenchService, ids []string) ([]string, error) {
-	logutil.GetLogger().Info("deleting job-submissions", "ids", ids)
+// BulkDeleteJobSubmissions deletes the job submissions with the given ids/names.
+func BulkDeleteJobSubmissions(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	logutil.GetLogger().Info("deleting job-submissions", "args", args)
+	ids, err := resolveJobSubmissionIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[[]string](
 		ctx,
 		s,
diff --git a/internal/services/workbench/job_submissions_test.go b/internal/services/workbench/job_submissions_test.go
index 22babeb..2b0461d 100644
--- a/internal/services/workbench/job_submissions_test.go
+++ b/internal/services/workbench/job_submissions_test.go
@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/sassoftware/wbctl/internal/constants"
+	"github.com/sassoftware/wbctl/internal/i18n"
 	"github.com/sassoftware/wbctl/internal/postgrest"
 	"github.com/sassoftware/wbctl/internal/services/workbench"
 	"github.com/sassoftware/wbctl/internal/stubs"
@@ -57,6 +58,15 @@ const (
     "current_state": "Queued",
     "runtime": 0.00,
     "log_url": "https://logs/controller"
+  },
+  {
+    "id": "js-job-name",
+    "controller_id": "ctrl-job-name",
+    "job_name": "job-name-job",
+    "username": "carol",
+    "current_state": "Queued",
+    "runtime": 0.00,
+    "log_url": "https://logs/controller"
   }
 ]
 `
@@ -334,6 +344,9 @@ Pod pod-1:
   Nominated node:   node-a
   IPs:              10.0.0.1
 `
+	jobRunResolveIDsQuery           = "or=(controller_id.in.(js-1,js-2),job_name.in.(js-1,js-2))"
+	jobRunResolveIDsResponseJSON    = `[{"id":"js-1"},{"id":"js-2"}]`
+	jobRunDeleteSuccessResponseJSON = `["js-1", "js-2"]`
 	expectedDeleteJobRunRequestJSON = `{"ids":["js-1", "js-2"]}`
 	expectedCreateJobRunRequestJSON = `{"param":{"job_name":"daily-job"}}`
 	createJobRunOneResponseBodyJSON = `{"id":"js-1","job_name":"daily-job"}`
@@ -353,7 +366,7 @@ var (
 		},
 	}
 
-	expectedResponseTwoSubmissions = types.JobSubmissions{
+	expectedResponseMixedSubmissions = types.JobSubmissions{
 		{
 			ID:           "123e4567-e89b-12d3-a456-426614174000",
 			ControllerID: "ctrl-uuid",
@@ -372,6 +385,15 @@ var (
 			Runtime:      0,
 			LogURL:       "https://logs/controller",
 		},
+		{
+			ID:           "js-job-name",
+			ControllerID: "ctrl-job-name",
+			JobName:      "job-name-job",
+			Username:     "carol",
+			CurrentState: "Queued",
+			Runtime:      0,
+			LogURL:       "https://logs/controller",
+		},
 	}
 )
 
@@ -383,9 +405,11 @@ func TestDeleteJobSubmission(t *testing.T) {
 				ids: []string{"js-1", "js-2"},
 			},
 			mockEndpoints: getJobSubmissionDeleteEndpoints(
+				jobRunResolveIDsQuery,
+				jobRunResolveIDsResponseJSON,
 				expectedDeleteJobRunRequestJSON,
 				http.StatusOK,
-				`["js-1", "js-2"]`,
+				jobRunDeleteSuccessResponseJSON,
 			),
 			expected: deleteExpected{
 				result: []string{"js-1", "js-2"},
@@ -397,6 +421,8 @@ func TestDeleteJobSubmission(t *testing.T) {
 				ids: []string{"js-1", "js-2"},
 			},
 			mockEndpoints: getJobSubmissionDeleteEndpoints(
+				jobRunResolveIDsQuery,
+				jobRunResolveIDsResponseJSON,
 				expectedDeleteJobRunRequestJSON,
 				http.StatusUnauthorized,
 				"unauthorized",
@@ -407,6 +433,25 @@ func TestDeleteJobSubmission(t *testing.T) {
 					"\t* 401 unauthorized",
 			},
 		},
+		{
+			name: "delete job-submission not found",
+			request: deleteRequest{
+				ids: []string{"missing"},
+			},
+			mockEndpoints: getJobSubmissionDeleteEndpoints(
+				`or=(controller_id.in.(missing),job_name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: deleteExpected{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.JobSubmissionType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -500,6 +545,22 @@ func TestGetJobSubmissions(t *testing.T) {
 				result: expectedResponseOneSubmission,
 			},
 		},
+		{
+			name: "get success with mixed uuid, controller ids, names query",
+			request: getRequest{
+				ids: []string{"123e4567-e89b-12d3-a456-426614174000", "controller-job-2", "job-name-job"},
+			},
+			mockEndpoints: getJobSubmissionsGetEndpoints(
+				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),"+
+					"controller_id.in.(controller-job-2,job-name-job),"+
+					"job_name.in.(123e4567-e89b-12d3-a456-426614174000,controller-job-2,job-name-job))",
+				http.StatusOK,
+				jobSubmissionsGetMixedIDsResponseJSON,
+			),
+			expected: getExpected[types.JobSubmissions]{
+				result: expectedResponseMixedSubmissions,
+			},
+		},
 		{
 			name: "sort by job_name asc",
 			request: getRequest{
@@ -668,32 +729,18 @@ func TestGetJobSubmissions(t *testing.T) {
 				result: expectedResponseOneSubmission,
 			},
 		},
-		{
-			name: "success with mixed uuid and controller ids query",
-			request: getRequest{
-				ids: []string{"123e4567-e89b-12d3-a456-426614174000", "controller-job-2"},
-			},
-			mockEndpoints: getJobSubmissionsGetEndpoints(
-				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),controller_id.in.(controller-job-2))",
-				http.StatusOK,
-				jobSubmissionsGetMixedIDsResponseJSON,
-			),
-			expected: getExpected[types.JobSubmissions]{
-				result: expectedResponseTwoSubmissions,
-			},
-		},
 		{
 			name: "invalid json response",
 			request: getRequest{
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getJobSubmissionsGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),job_name.in.(id-a))",
 				http.StatusOK,
 				"[",
 			),
 			expected: getExpected[types.JobSubmissions]{
-				err: "GET ${URL}/postgrest/job_runs?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/job_runs?or=%28controller_id.in.%28id-a%29%2Cjob_name.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* unexpected EOF",
 			},
@@ -704,12 +751,12 @@ func TestGetJobSubmissions(t *testing.T) {
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getJobSubmissionsGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),job_name.in.(id-a))",
 				http.StatusUnauthorized,
 				"unauthorized",
 			),
 			expected: getExpected[types.JobSubmissions]{
-				err: "GET ${URL}/postgrest/job_runs?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/job_runs?or=%28controller_id.in.%28id-a%29%2Cjob_name.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
@@ -852,12 +899,14 @@ func TestDescribeJobSubmissions(t *testing.T) {
 }
 
 func getJobSubmissionDeleteEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getJobSubmissionsGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCJobRunBulkDelete),
@@ -868,8 +917,7 @@ func getJobSubmissionDeleteEndpoints(
 				Body:   responseBody,
 				Status: responseStatus,
 			},
-		},
-	}
+		})
 }
 
 func getJobSubmissionCreateEndpoints(
diff --git a/internal/services/workbench/jobs.go b/internal/services/workbench/jobs.go
index a5b3010..9230363 100644
--- a/internal/services/workbench/jobs.go
+++ b/internal/services/workbench/jobs.go
@@ -18,28 +18,45 @@ import (
 	"github.com/sassoftware/wbctl/internal/ui"
 )
 
-// GetJobs gets the jobs in the given WorkbenchService. If given any ids, only
-// the matching resources will be returned. Otherwise, all resources are
+// GetJobs gets the jobs in the given WorkbenchService. If given any ids/names,
+// only the matching resources will be returned. Otherwise, all resources are
 // returned.
 func GetJobs(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	sortSpec string,
 ) (types.Jobs, error) {
-	logutil.GetLogger().Info("getting jobs", "ids", ids, "sort", sortSpec)
+	logutil.GetLogger().Info("getting jobs", "args", args, "sort", sortSpec)
 	sortSpec = normalizeSortSpec(sortSpec, map[string][]string{
 		constants.SortKeyAge:   {constants.SortKeyCreatedAt},
 		constants.SortKeyState: {constants.SortKeyCurrentState},
 	})
-	return result.Split(Get[types.Jobs](
-		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointJobs), IDsQuery(ids, sortSpec),
+	jobs, err := result.Split(Get[types.Jobs](
+		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointJobs), IDsOrNamesQuery(args, sortSpec),
 	))
+	if err != nil {
+		return nil, err
+	}
+	return selectByIDThenName(
+		args,
+		jobs,
+	), nil
+}
+
+func resolveJobIDs(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	return resolveIDs(
+		ctx, s, args, GetJobs, types.Job.GetID, constants.JobType,
+	)
 }
 
-// BulkDeleteJobs deletes the job with the given id.
-func BulkDeleteJobs(ctx context.Context, s *WorkbenchService, ids []string) ([]string, error) {
-	logutil.GetLogger().Info("deleting jobs", "ids", ids)
+// BulkDeleteJobs deletes the jobs with the given ids/names.
+func BulkDeleteJobs(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	logutil.GetLogger().Info("deleting jobs", "args", args)
+	ids, err := resolveJobIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[[]string](
 		ctx,
 		s,
@@ -59,15 +76,19 @@ func CreateJob(ctx context.Context, s *WorkbenchService, body types.Job) (types.
 	))
 }
 
-// BulkUpdateJobs  applies the given types.Job to all the jobs with the given
-// ids.
+// BulkUpdateJobs applies the given types.Job to all the jobs with the given
+// ids/names.
 func BulkUpdateJobs(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	body types.Job,
 ) (types.Jobs, error) {
-	logutil.GetLogger().Info("updating jobs", "ids", ids, "body", body)
+	logutil.GetLogger().Info("updating jobs", "args", args, "body", body)
+	ids, err := resolveJobIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[types.Jobs](
 		ctx,
 		s,
diff --git a/internal/services/workbench/jobs_test.go b/internal/services/workbench/jobs_test.go
index 00f2c90..dcf4a26 100644
--- a/internal/services/workbench/jobs_test.go
+++ b/internal/services/workbench/jobs_test.go
@@ -12,6 +12,7 @@ import (
 	"time"
 
 	"github.com/sassoftware/wbctl/internal/constants"
+	"github.com/sassoftware/wbctl/internal/i18n"
 	"github.com/sassoftware/wbctl/internal/postgrest"
 	"github.com/sassoftware/wbctl/internal/services/workbench"
 	"github.com/sassoftware/wbctl/internal/stubs"
@@ -34,17 +35,17 @@ const (
 ]
 `
 
-	jobsGetTwoResponseJSON = `
+	jobsGetMixedResponseJSON = `
 [
   {
-    "id": "id-a",
-    "name": "job-a",
-    "application_id": "app-a"
+    "id": "123e4567-e89b-12d3-a456-426614174000",
+    "name": "job-uuid",
+    "application_id": "app-uuid"
   },
   {
-    "id": "id-b",
-    "name": "job-b",
-    "application_id": "app-b"
+    "id": "job-name-id",
+    "name": "job-name",
+    "application_id": "app-name"
   }
 ]
 `
@@ -352,6 +353,9 @@ Schedule:                 0 * * * *
   }
 ]
 `
+	jobsResolveIDsQuery              = "or=(name.in.(job-1,job-2))"
+	jobsResolveIDsResponseJSON       = `[{"id":"job-1"},{"id":"job-2"}]`
+	jobsDeleteSuccessResponseJSON    = `["job-1", "job-2"]`
 	expectedDeleteJobRequestJSON     = `{"ids":["job-1","job-2"]}`
 	createJobOneResponseBodyJSON     = `{"id":"job-1","name":"daily-job"}`
 	expectedCreateJobRequestJSON     = `{"param":{"name":"daily-job"}}`
@@ -363,9 +367,9 @@ var (
 		{ID: "job-1", Name: "daily-job", Description: "first", ApplicationID: "app-1"},
 	}
 
-	expectedResponseTwoJobs = types.Jobs{
-		{ID: "id-a", Name: "job-a", ApplicationID: "app-a"},
-		{ID: "id-b", Name: "job-b", ApplicationID: "app-b"},
+	expectedResponseMixedJobs = types.Jobs{
+		{ID: "123e4567-e89b-12d3-a456-426614174000", Name: "job-uuid", ApplicationID: "app-uuid"},
+		{ID: "job-name-id", Name: "job-name", ApplicationID: "app-name"},
 	}
 )
 
@@ -377,9 +381,11 @@ func TestDeleteJob(t *testing.T) {
 				ids: []string{"job-1", "job-2"},
 			},
 			mockEndpoints: getJobsDeleteEndpoints(
+				jobsResolveIDsQuery,
+				jobsResolveIDsResponseJSON,
 				expectedDeleteJobRequestJSON,
 				http.StatusOK,
-				`["job-1", "job-2"]`,
+				jobsDeleteSuccessResponseJSON,
 			),
 			expected: deleteExpected{
 				result: []string{"job-1", "job-2"},
@@ -391,6 +397,8 @@ func TestDeleteJob(t *testing.T) {
 				ids: []string{"job-1", "job-2"},
 			},
 			mockEndpoints: getJobsDeleteEndpoints(
+				jobsResolveIDsQuery,
+				jobsResolveIDsResponseJSON,
 				expectedDeleteJobRequestJSON,
 				http.StatusUnauthorized,
 				"unauthorized",
@@ -401,6 +409,25 @@ func TestDeleteJob(t *testing.T) {
 					"\t* 401 unauthorized",
 			},
 		},
+		{
+			name: "delete jobs not found",
+			request: deleteRequest{
+				ids: []string{"missing"},
+			},
+			mockEndpoints: getJobsDeleteEndpoints(
+				`or=(name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: deleteExpected{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.JobType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -489,6 +516,8 @@ func TestBulkUpdateJobs(t *testing.T) {
 				body: types.Job{Name: "updated-name"},
 			},
 			mockEndpoints: getJobsBulkUpdateEndpoints(
+				jobsResolveIDsQuery,
+				jobsResolveIDsResponseJSON,
 				expectedBulkUpdateJobRequestJSON,
 				http.StatusOK,
 				jobsBulkUpdateResponseJSON,
@@ -507,6 +536,8 @@ func TestBulkUpdateJobs(t *testing.T) {
 				body: types.Job{Name: "updated-name"},
 			},
 			mockEndpoints: getJobsBulkUpdateEndpoints(
+				jobsResolveIDsQuery,
+				jobsResolveIDsResponseJSON,
 				expectedBulkUpdateJobRequestJSON,
 				http.StatusUnauthorized,
 				"unauthorized",
@@ -524,6 +555,8 @@ func TestBulkUpdateJobs(t *testing.T) {
 				body: types.Job{Name: "updated-name"},
 			},
 			mockEndpoints: getJobsBulkUpdateEndpoints(
+				jobsResolveIDsQuery,
+				jobsResolveIDsResponseJSON,
 				expectedBulkUpdateJobRequestJSON,
 				http.StatusOK,
 				"[",
@@ -534,6 +567,26 @@ func TestBulkUpdateJobs(t *testing.T) {
 					"\t* unexpected EOF",
 			},
 		},
+		{
+			name: "bulk update jobs not found",
+			request: bulkupdateRequest[types.Job]{
+				ids:  []string{"missing"},
+				body: types.Job{Name: "updated-name"},
+			},
+			mockEndpoints: getJobsBulkUpdateEndpoints(
+				`or=(name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: bulkupdateExpected[types.Jobs]{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.JobType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -567,15 +620,19 @@ func TestGetJobs(t *testing.T) {
 			},
 		},
 		{
-			name:    "get jobs success with ids query",
-			request: getRequest{ids: []string{"id-a"}},
+			name: "get jobs success with mixed ids, names query",
+			request: getRequest{
+				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "job-name"},
+				sort: "id.asc",
+			},
 			mockEndpoints: getJobsGetEndpoints(
-				"id=in.(id-a)",
+				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),"+
+					"name.in.(123e4567-e89b-12d3-a456-426614174000,job-name))&order=id.asc",
 				http.StatusOK,
-				jobsGetTwoResponseJSON,
+				jobsGetMixedResponseJSON,
 			),
 			expected: getExpected[types.Jobs]{
-				result: expectedResponseTwoJobs,
+				result: expectedResponseMixedJobs,
 			},
 		},
 		{
@@ -666,14 +723,14 @@ func TestGetJobs(t *testing.T) {
 			name:    "get jobs invalid json response",
 			request: getRequest{ids: []string{"id-a"}},
 			mockEndpoints: getJobsGetEndpoints(
-				"id=in.(id-a)",
+				"or=(name.in.(id-a))",
 				http.StatusOK,
 				"[",
 			),
 			expected: getExpected[types.Jobs]{
 				// jobs do not have controller ids so this query is for `id`
 				// even though it is not a uuid.
-				err: "GET ${URL}/postgrest/jobs?id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/jobs?or=%28name.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* unexpected EOF",
 			},
@@ -682,12 +739,12 @@ func TestGetJobs(t *testing.T) {
 			name:    "get jobs non-200 from server",
 			request: getRequest{ids: []string{"id-a"}},
 			mockEndpoints: getJobsGetEndpoints(
-				"id=in.(id-a)",
+				"or=(name.in.(id-a))",
 				http.StatusUnauthorized,
 				"unauthorized",
 			),
 			expected: getExpected[types.Jobs]{
-				err: "GET ${URL}/postgrest/jobs?id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/jobs?or=%28name.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
@@ -852,12 +909,14 @@ func TestDescribeJobs(t *testing.T) {
 }
 
 func getJobsDeleteEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getJobsGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCJobBulkDelete),
@@ -868,8 +927,7 @@ func getJobsDeleteEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getJobsCreateEndpoints(
@@ -894,12 +952,14 @@ func getJobsCreateEndpoints(
 }
 
 func getJobsBulkUpdateEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getJobsGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCJobBulkUpdate),
@@ -910,8 +970,7 @@ func getJobsBulkUpdateEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getJobsGetEndpoints(
diff --git a/internal/services/workbench/snapshots.go b/internal/services/workbench/snapshots.go
index a778dcd..1debda5 100644
--- a/internal/services/workbench/snapshots.go
+++ b/internal/services/workbench/snapshots.go
@@ -22,29 +22,47 @@ import (
 )
 
 // GetSnapshots gets the snapshots in the given WorkbenchService. If given any
-// ids, only the matching resources will be returned. Otherwise, all resources
+// ids/names, only the matching resources will be returned. Otherwise, all resources
 // are returned.
+// nolint:dupl
 func GetSnapshots(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	sortSpec string,
 ) (types.Snapshots, error) {
-	logutil.GetLogger().Info("getting snapshots", "ids", ids, "sort", sortSpec)
+	logutil.GetLogger().Info("getting snapshots", "args", args, "sort", sortSpec)
 	sortSpec = normalizeSortSpec(sortSpec, map[string][]string{
 		constants.SortKeyAge:    {constants.SortKeyCreatedAt},
 		constants.SortKeySource: {constants.SortKeySpecSourcePVCName},
 		constants.SortKeySize:   {constants.SortKeyVolumeStatusRestoreSize},
 		constants.SortKeyReady:  {constants.SortKeyVolumeStatusReadyToUse},
 	})
-	return result.Split(Get[types.Snapshots](
-		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointSnapshots), IDsOrControllerIDsQuery(ids, sortSpec),
+	snapshots, err := result.Split(Get[types.Snapshots](
+		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointSnapshots), IDsControllerIDsOrNamesQuery(args, sortSpec),
 	))
+	if err != nil {
+		return nil, err
+	}
+	return selectByIDThenName(
+		args,
+		snapshots,
+	), nil
+}
+
+func resolveSnapshotIDs(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	return resolveIDs(
+		ctx, s, args, GetSnapshots, types.Snapshot.GetID, constants.SnapshotType,
+	)
 }
 
-// BulkDeleteSnapshots deletes the snapshot with the given id.
-func BulkDeleteSnapshots(ctx context.Context, s *WorkbenchService, ids []string) ([]string, error) {
-	logutil.GetLogger().Info("deleting snapshots", "ids", ids)
+// BulkDeleteSnapshots deletes the snapshots with the given ids/names.
+func BulkDeleteSnapshots(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	logutil.GetLogger().Info("deleting snapshots", "args", args)
+	ids, err := resolveSnapshotIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[[]string](
 		ctx,
 		s,
@@ -69,14 +87,18 @@ func CreateSnapshot(
 }
 
 // BulkUpdateSnapshots applies the given types.Snapshot to all the snapshots
-// with the given ids.
+// with the given ids/names.
 func BulkUpdateSnapshots(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	body types.Snapshot,
 ) (types.Snapshots, error) {
-	logutil.GetLogger().Info("updating snapshots", "ids", ids, "body", body)
+	logutil.GetLogger().Info("updating snapshots", "args", args, "body", body)
+	ids, err := resolveSnapshotIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[types.Snapshots](
 		ctx,
 		s,
diff --git a/internal/services/workbench/snapshots_test.go b/internal/services/workbench/snapshots_test.go
index 3311d41..e032127 100644
--- a/internal/services/workbench/snapshots_test.go
+++ b/internal/services/workbench/snapshots_test.go
@@ -6,13 +6,13 @@ package workbench_test
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"strings"
 	"testing"
 	"time"
 
 	"github.com/sassoftware/wbctl/internal/constants"
+	"github.com/sassoftware/wbctl/internal/i18n"
 	"github.com/sassoftware/wbctl/internal/postgrest"
 	"github.com/sassoftware/wbctl/internal/services/workbench"
 	"github.com/sassoftware/wbctl/internal/stubs"
@@ -51,6 +51,13 @@ const (
     "name": "snapshot-controller",
     "username": "bob",
     "description": "controller"
+  },
+  {
+    "id": "snap-name",
+    "controller_id": "ctrl-name",
+    "name": "snapshot-name",
+    "username": "carol",
+    "description": "name"
   }
 ]
 `
@@ -333,6 +340,9 @@ Volume Status:
   Content Name:    
   Creation Time:   
 `
+	snapshotResolveIDsQuery               = "or=(controller_id.in.(snap-1,snap-2),name.in.(snap-1,snap-2))"
+	snapshotResolveIDsResponseJSON        = `[{"id":"snap-1"},{"id":"snap-2"}]`
+	snapshotDeleteSuccessResponseJSON     = `["snap-1", "snap-2"]`
 	expectedDeleteSnapshotRequestJSON     = `{"ids":["snap-1","snap-2"]}`
 	createSnapshotOneResponseBodyJSON     = `{"id":"snap-1","name":"snapshot-1"}`
 	expectedCreateSnapshotRequestJSON     = `{"param":{"name":"snapshot-1"}}`
@@ -350,7 +360,7 @@ var (
 		},
 	}
 
-	expectedResponseTwoSnapshots = types.Snapshots{
+	expectedResponseMixedSnapshots = types.Snapshots{
 		{
 			ID:           "123e4567-e89b-12d3-a456-426614174000",
 			ControllerID: "ctrl-uuid",
@@ -365,6 +375,13 @@ var (
 			Username:     "bob",
 			Description:  "controller",
 		},
+		{
+			ID:           "snap-name",
+			ControllerID: "ctrl-name",
+			Name:         "snapshot-name",
+			Username:     "carol",
+			Description:  "name",
+		},
 	}
 )
 
@@ -376,9 +393,11 @@ func TestDeleteSnapshot(t *testing.T) {
 				ids: []string{"snap-1", "snap-2"},
 			},
 			mockEndpoints: getSnapshotDeleteEndpoints(
+				snapshotResolveIDsQuery,
+				snapshotResolveIDsResponseJSON,
 				expectedDeleteSnapshotRequestJSON,
 				http.StatusOK,
-				`["snap-1","snap-2"]`,
+				snapshotDeleteSuccessResponseJSON,
 			),
 			expected: deleteExpected{
 				result: []string{"snap-1", "snap-2"},
@@ -390,6 +409,8 @@ func TestDeleteSnapshot(t *testing.T) {
 				ids: []string{"snap-1", "snap-2"},
 			},
 			mockEndpoints: getSnapshotDeleteEndpoints(
+				snapshotResolveIDsQuery,
+				snapshotResolveIDsResponseJSON,
 				expectedDeleteSnapshotRequestJSON,
 				http.StatusUnauthorized,
 				`unauthorized`,
@@ -400,6 +421,25 @@ func TestDeleteSnapshot(t *testing.T) {
 					"\t* 401 unauthorized",
 			},
 		},
+		{
+			name: "delete snapshot not found",
+			request: deleteRequest{
+				ids: []string{"missing"},
+			},
+			mockEndpoints: getSnapshotDeleteEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: deleteExpected{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.SnapshotType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -486,6 +526,8 @@ func TestBulkUpdateSnapshots(t *testing.T) {
 				body: types.Snapshot{Name: "updated-name"},
 			},
 			mockEndpoints: getSnapshotBulkUpdateEndpoints(
+				snapshotResolveIDsQuery,
+				snapshotResolveIDsResponseJSON,
 				expectedBulkUpdateSnapshotRequestJSON,
 				http.StatusOK,
 				snapshotsBulkUpdateResponseJSON,
@@ -504,6 +546,8 @@ func TestBulkUpdateSnapshots(t *testing.T) {
 				body: types.Snapshot{Name: "updated-name"},
 			},
 			mockEndpoints: getSnapshotBulkUpdateEndpoints(
+				snapshotResolveIDsQuery,
+				snapshotResolveIDsResponseJSON,
 				expectedBulkUpdateSnapshotRequestJSON,
 				http.StatusUnauthorized,
 				`unauthorized`,
@@ -521,6 +565,8 @@ func TestBulkUpdateSnapshots(t *testing.T) {
 				body: types.Snapshot{Name: "updated-name"},
 			},
 			mockEndpoints: getSnapshotBulkUpdateEndpoints(
+				snapshotResolveIDsQuery,
+				snapshotResolveIDsResponseJSON,
 				expectedBulkUpdateSnapshotRequestJSON,
 				http.StatusOK,
 				`[`,
@@ -531,6 +577,26 @@ func TestBulkUpdateSnapshots(t *testing.T) {
 					"\t* unexpected EOF",
 			},
 		},
+		{
+			name: "bulk update snapshots not found",
+			request: bulkupdateRequest[types.Snapshot]{
+				ids:  []string{"missing"},
+				body: types.Snapshot{Name: "updated-name"},
+			},
+			mockEndpoints: getSnapshotBulkUpdateEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: bulkupdateExpected[types.Snapshots]{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.SnapshotType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -564,19 +630,20 @@ func TestGetSnapshots(t *testing.T) {
 			},
 		},
 		{
-			name: "get snapshots success with mixed uuid and controller ids query",
+			name: "get snapshots success with mixed uuid, controller id, names query",
 			request: getRequest{
-				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-snapshot-2"},
+				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-snapshot-2", "snapshot-name"},
 				sort: "id.asc",
 			},
 			mockEndpoints: getSnapshotsGetEndpoints(
 				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),"+
-					"controller_id.in.(controller-snapshot-2))&order=id.asc",
+					"controller_id.in.(controller-snapshot-2,snapshot-name),"+
+					"name.in.(123e4567-e89b-12d3-a456-426614174000,controller-snapshot-2,snapshot-name))&order=id.asc",
 				http.StatusOK,
 				snapshotsGetMixedIDsResponseJSON,
 			),
 			expected: getExpected[types.Snapshots]{
-				result: expectedResponseTwoSnapshots,
+				result: expectedResponseMixedSnapshots,
 			},
 		},
 		{
@@ -712,12 +779,12 @@ func TestGetSnapshots(t *testing.T) {
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getSnapshotsGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),name.in.(id-a))",
 				http.StatusOK,
 				`[`,
 			),
 			expected: getExpected[types.Snapshots]{
-				err: "GET %s/postgrest/snapshots?controller_id=in.%%28id-a%%29 failed\n" +
+				err: "GET ${URL}/postgrest/snapshots?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* unexpected EOF",
 			},
@@ -728,12 +795,12 @@ func TestGetSnapshots(t *testing.T) {
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getSnapshotsGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),name.in.(id-a))",
 				http.StatusUnauthorized,
 				`unauthorized`,
 			),
 			expected: getExpected[types.Snapshots]{
-				err: "GET %s/postgrest/snapshots?controller_id=in.%%28id-a%%29 failed\n" +
+				err: "GET ${URL}/postgrest/snapshots?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
@@ -748,7 +815,7 @@ func TestGetSnapshots(t *testing.T) {
 
 			actual, err := workbench.GetSnapshots(context.Background(), svc, tt.request.ids, tt.request.sort)
 			if tt.expected.err != "" {
-				require.EqualError(t, err, fmt.Sprintf(tt.expected.err, srv.URL))
+				require.EqualError(t, err, strings.ReplaceAll(tt.expected.err, "${URL}", srv.URL))
 				return
 			}
 			require.NoError(t, err)
@@ -872,12 +939,14 @@ func TestDescribeSnapshots(t *testing.T) {
 }
 
 func getSnapshotDeleteEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getSnapshotsGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCSnapshotBulkDelete),
@@ -888,8 +957,7 @@ func getSnapshotDeleteEndpoints(
 				Body:   responseBody,
 				Status: responseStatus,
 			},
-		},
-	}
+		})
 }
 
 func getSnapshotCreateEndpoints(
@@ -914,12 +982,14 @@ func getSnapshotCreateEndpoints(
 }
 
 func getSnapshotBulkUpdateEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getSnapshotsGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCSnapshotBulkUpdate),
@@ -930,8 +1000,7 @@ func getSnapshotBulkUpdateEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getSnapshotsGetEndpoints(
diff --git a/internal/services/workbench/util.go b/internal/services/workbench/util.go
index 07e8b8d..497a548 100644
--- a/internal/services/workbench/util.go
+++ b/internal/services/workbench/util.go
@@ -34,42 +34,144 @@ func IDsQuery(ids []string, sortSpec string) url.Values {
 	return query
 }
 
-// IDsOrControllerIDsQuery returns a PostgREST compatible query for the given
-// IDs and sort spec that will match either the actual id or the controller_id
+// IDsControllerIDsOrNamesQuery returns a PostgREST compatible query for the
+// given IDs/names and sort spec that will match the id, controller_id, or name
 // columns.
-func IDsOrControllerIDsQuery(ids []string, sortSpec string) url.Values {
+func IDsControllerIDsOrNamesQuery(args []string, sortSpec string) url.Values {
+	return buildFilterQuery(args, "controller_id", "name", sortSpec)
+}
+
+// IDsOrNamesQuery returns a PostgREST compatible query for the given IDs/names
+// and sort spec that will match the id or name columns.
+func IDsOrNamesQuery(args []string, sortSpec string) url.Values {
+	return buildFilterQuery(args, "", "name", sortSpec)
+}
+
+// IDsControllerIDsOrJobNamesQuery returns a PostgREST compatible query for the
+// given IDs/names and sort spec that will match the id, controller_id, or job_name
+// columns.
+func IDsControllerIDsOrJobNamesQuery(args []string, sortSpec string) url.Values {
+	return buildFilterQuery(args, "controller_id", "job_name", sortSpec)
+}
+
+// buildFilterQuery generates the query for the given IDs/names. UUID values
+// are matched against the id column, while other values are matched against
+// the controllerIDColumn if provided. Every value is matched against the
+// nameColumn.
+func buildFilterQuery(args []string, controllerIDColumn, nameColumn, sortSpec string) url.Values {
 	query := url.Values{}
 	if sortSpec != "" {
 		query["order"] = []string{sortSpec}
 	}
+	if len(args) == 0 {
+		return query
+	}
 	uuidIDs := []string{}
-	controllerIDs := []string{}
-	for _, id := range ids {
-		if isUUID(id) {
-			uuidIDs = append(uuidIDs, id)
+	nonUUIDs := []string{}
+	for _, arg := range args {
+		if isUUID(arg) {
+			uuidIDs = append(uuidIDs, arg)
 		} else {
-			controllerIDs = append(controllerIDs, id)
+			nonUUIDs = append(nonUUIDs, arg)
 		}
 	}
-	switch {
-	case len(uuidIDs) > 0 && len(controllerIDs) > 0:
-		query["or"] = []string{
-			"(id.in.(" + strings.Join(
-				uuidIDs,
-				",",
-			) + "),controller_id.in.(" + strings.Join(
-				controllerIDs,
-				",",
-			) + "))",
-		}
-	case len(uuidIDs) > 0:
-		query["id"] = []string{"in.(" + strings.Join(uuidIDs, ",") + ")"}
-	case len(controllerIDs) > 0:
-		query["controller_id"] = []string{"in.(" + strings.Join(controllerIDs, ",") + ")"}
+	clauses := []string{}
+	if len(uuidIDs) > 0 {
+		clauses = append(clauses, "id.in.("+strings.Join(uuidIDs, ",")+")")
 	}
+	if controllerIDColumn != "" && len(nonUUIDs) > 0 {
+		clauses = append(clauses, controllerIDColumn+".in.("+strings.Join(nonUUIDs, ",")+")")
+	}
+	clauses = append(clauses, nameColumn+".in.("+strings.Join(args, ",")+")")
+	query["or"] = []string{"(" + strings.Join(clauses, ",") + ")"}
 	return query
 }
 
+// selectByIDThenName narrows resources to those requested by args. It provides precedence
+// for selecting by ID > Name. Each arg is resolved independently and no resource is
+// returned twice.
+func selectByIDThenName[T types.IdentifiableResource, TS ~[]T](
+	args []string,
+	resources TS,
+) TS {
+	if len(args) == 0 {
+		return resources
+	}
+	selected := make([]bool, len(resources))
+	for _, arg := range args {
+		if arg == "" {
+			continue
+		}
+		for _, i := range matchIndexesByIDThenName(arg, resources) {
+			selected[i] = true
+		}
+	}
+	filtered := make(TS, 0, len(resources))
+	for i, resource := range resources {
+		if selected[i] {
+			filtered = append(filtered, resource)
+		}
+	}
+	return filtered
+}
+
+// matchIndexesByIDThenName returns the indexes of the resources matching args.
+// Resources matching by ID or controller ID take precedence over those
+// matching by name.
+func matchIndexesByIDThenName[T types.IdentifiableResource, TS ~[]T](
+	arg string,
+	resources TS,
+) []int {
+	var byID, byName []int
+	for i, resource := range resources {
+		switch {
+		case resource.GetID() == arg || resource.GetControllerID() == arg:
+			byID = append(byID, i)
+		case resource.GetName() == arg:
+			byName = append(byName, i)
+		}
+	}
+	if len(byID) > 0 {
+		return byID
+	}
+	return byName
+}
+
+// resolveIDs converts the given args, which may be ids, controller_ids, or names,
+// into the database ids of the matching resources.
+func resolveIDs[T any, TS ~[]T](
+	ctx context.Context,
+	s *WorkbenchService,
+	args []string,
+	getter func(context.Context, *WorkbenchService, []string, string) (TS, error),
+	getIDFunc func(T) string,
+	resourceType string,
+) ([]string, error) {
+	if len(args) == 0 {
+		return args, nil
+	}
+	resources, err := getter(ctx, s, args, "")
+	if err != nil {
+		return nil, err
+	}
+	if len(resources) == 0 {
+		return nil, errors.New(
+			i18n.Format(
+				i18n.ResourceNotFoundWithID,
+				i18n.A{
+					constants.I18nArgTypeKey: resourceType,
+					constants.I18nArgKeyID:   strings.Join(args, ", "),
+				},
+			),
+		)
+	}
+	resolved := make([]string, 0, len(resources))
+	for _, resource := range resources {
+		resolved = append(resolved, getIDFunc(resource))
+	}
+	return resolved, nil
+}
+
 // Auth0IDsQuery returns an Auth0 compatible query for the given IDs.
 func Auth0IDsQuery(ids []string, sortSpec string) url.Values {
 	if len(ids) == 0 && sortSpec == "" {
diff --git a/internal/services/workbench/util_internal_test.go b/internal/services/workbench/util_internal_test.go
new file mode 100644
index 0000000..7643536
--- /dev/null
+++ b/internal/services/workbench/util_internal_test.go
@@ -0,0 +1,180 @@
+// Copyright © 2026, SAS Institute Inc., Cary, NC, USA.  All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+package workbench
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/sassoftware/wbctl/internal/constants"
+	"github.com/sassoftware/wbctl/internal/i18n"
+	"github.com/stretchr/testify/require"
+)
+
+type testResource struct {
+	ID           string
+	ControllerID string
+	Name         string
+}
+
+type testResources []testResource
+
+func (r testResource) GetID() string           { return r.ID }
+func (r testResource) GetControllerID() string { return r.ControllerID }
+func (r testResource) GetName() string         { return r.Name }
+
+func TestSelectByIDThenName(t *testing.T) {
+	a := testResource{ID: "id-a", ControllerID: "ctrl-a", Name: "name-a"}
+	b := testResource{ID: "id-b", ControllerID: "ctrl-b", Name: "name-b"}
+	// c has a name equal to a's ID, to exercise ID > name precedence.
+	c := testResource{ID: "id-c", ControllerID: "ctrl-c", Name: "id-a"}
+	// d shares a name with b.
+	d := testResource{ID: "id-d", ControllerID: "ctrl-d", Name: "name-b"}
+
+	tests := []struct {
+		name      string
+		args      []string
+		resources testResources
+		expected  testResources
+	}{
+		{
+			name:      "no args returns all resources",
+			args:      nil,
+			resources: testResources{a, b},
+			expected:  testResources{a, b},
+		},
+		{
+			name:      "empty string args are skipped",
+			args:      []string{"", "id-a"},
+			resources: testResources{a, b},
+			expected:  testResources{a},
+		},
+		{
+			name:      "match by id",
+			args:      []string{"id-a"},
+			resources: testResources{a, b},
+			expected:  testResources{a},
+		},
+		{
+			name:      "match by controller id",
+			args:      []string{"ctrl-b"},
+			resources: testResources{a, b},
+			expected:  testResources{b},
+		},
+		{
+			name:      "match by name",
+			args:      []string{"name-a"},
+			resources: testResources{a, b},
+			expected:  testResources{a},
+		},
+		{
+			name:      "id takes precedence over name",
+			args:      []string{"id-a"},
+			resources: testResources{a, c},
+			expected:  testResources{a},
+		},
+		{
+			name:      "name used when no id matches",
+			args:      []string{"name-b"},
+			resources: testResources{a, b, d},
+			expected:  testResources{b, d},
+		},
+		{
+			name:      "no match returns empty",
+			args:      []string{"missing"},
+			resources: testResources{a, b},
+			expected:  testResources{},
+		},
+		{
+			name:      "multiple args are resolved independently",
+			args:      []string{"id-a", "name-b"},
+			resources: testResources{a, b, c, d},
+			expected:  testResources{a, b, d},
+		},
+		{
+			name:      "resource matched by several args is returned once",
+			args:      []string{"id-a", "name-a", "ctrl-a"},
+			resources: testResources{a, b},
+			expected:  testResources{a},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual := selectByIDThenName(
+				tt.args,
+				tt.resources,
+			)
+			require.Equal(t, tt.expected, actual)
+		})
+	}
+}
+
+func TestResolveIDs(t *testing.T) {
+	tests := []struct {
+		name        string
+		args        []string
+		getter      func(context.Context, *WorkbenchService, []string, string) (testResources, error)
+		expected    []string
+		expectedErr string
+	}{
+		{
+			name: "empty args returns args without calling getter",
+			args: nil,
+			getter: func(context.Context, *WorkbenchService, []string, string) (testResources, error) {
+				t.Error("getter must not be called")
+				return nil, nil
+			},
+			expected: nil,
+		},
+		{
+			name: "resolves to ids",
+			args: []string{"name-a", "ctrl-b"},
+			getter: func(_ context.Context, _ *WorkbenchService, args []string, sortSpec string) (testResources, error) {
+				require.Equal(t, []string{"name-a", "ctrl-b"}, args)
+				require.Empty(t, sortSpec)
+				return testResources{{ID: "id-a"}, {ID: "id-b"}}, nil
+			},
+			expected: []string{"id-a", "id-b"},
+		},
+		{
+			name: "getter error is returned",
+			args: []string{"name-a"},
+			getter: func(context.Context, *WorkbenchService, []string, string) (testResources, error) {
+				return nil, errors.New("getter err")
+			},
+			expectedErr: "getter err",
+		},
+		{
+			name: "no matches returns not found error",
+			args: []string{"ws-1", "ws-2"},
+			getter: func(context.Context, *WorkbenchService, []string, string) (testResources, error) {
+				return testResources{}, nil
+			},
+			expectedErr: i18n.Format(
+				i18n.ResourceNotFoundWithID,
+				i18n.A{
+					constants.I18nArgTypeKey: constants.VolumeType,
+					constants.I18nArgKeyID:   "ws-1, ws-2",
+				},
+			),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual, err := resolveIDs(
+				context.Background(), nil, tt.args, tt.getter, testResource.GetID, constants.VolumeType,
+			)
+			if tt.expectedErr != "" {
+				require.EqualError(t, err, tt.expectedErr)
+				require.Nil(t, actual)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, tt.expected, actual)
+		})
+	}
+}
diff --git a/internal/services/workbench/util_test.go b/internal/services/workbench/util_test.go
index e1de8da..dfb97f8 100644
--- a/internal/services/workbench/util_test.go
+++ b/internal/services/workbench/util_test.go
@@ -11,7 +11,39 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestIDsOrControllerIDsQuery(t *testing.T) {
+func TestIDsQuery(t *testing.T) {
+	tests := []struct {
+		name     string
+		ids      []string
+		expected url.Values
+		sort     string
+	}{
+		{
+			name:     "empty ids",
+			ids:      nil,
+			expected: url.Values{},
+		},
+		{
+			name:     "single id",
+			ids:      []string{"id-a"},
+			expected: url.Values{"id": []string{"in.(id-a)"}},
+		},
+		{
+			name:     "multiple ids",
+			ids:      []string{"id-a", "id-b"},
+			expected: url.Values{"id": []string{"in.(id-a,id-b)"}},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual := workbench.IDsQuery(tt.ids, tt.sort)
+			require.Equal(t, tt.expected, actual)
+		})
+	}
+}
+
+func TestIDsControllerIDsOrNamesQuery(t *testing.T) {
 	tests := []struct {
 		name     string
 		ids      []string
@@ -28,38 +60,100 @@ func TestIDsOrControllerIDsQuery(t *testing.T) {
 			ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"},
 			sort: "id.asc",
 			expected: url.Values{
-				"id":    []string{"in.(123e4567-e89b-12d3-a456-426614174000,123e4567-e89b-12d3-a456-426614174001)"},
+				"or": []string{
+					"(id.in.(123e4567-e89b-12d3-a456-426614174000,123e4567-e89b-12d3-a456-426614174001)," +
+						"name.in.(123e4567-e89b-12d3-a456-426614174000,123e4567-e89b-12d3-a456-426614174001))",
+				},
 				"order": []string{"id.asc"},
 			},
 		},
 		{
-			name:     "all controller ids",
-			ids:      []string{"controller-a", "controller-b"},
-			expected: url.Values{"controller_id": []string{"in.(controller-a,controller-b)"}},
+			name: "all controller ids / names",
+			ids:  []string{"controller-a", "name-a"},
+			expected: url.Values{
+				"or": []string{
+					"(controller_id.in.(controller-a,name-a),name.in.(controller-a,name-a))",
+				},
+			},
 		},
 		{
-			name: "mixed uuid and controller ids",
-			ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-a"},
+			name: "mixed uuid, controller id, names",
+			ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-a", "name-a"},
 			expected: url.Values{
-				"or": []string{"(id.in.(123e4567-e89b-12d3-a456-426614174000),controller_id.in.(controller-a))"},
+				"or": []string{
+					"(id.in.(123e4567-e89b-12d3-a456-426614174000)," +
+						"controller_id.in.(controller-a,name-a)," +
+						"name.in.(123e4567-e89b-12d3-a456-426614174000,controller-a,name-a))",
+				},
 			},
 		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			actual := workbench.IDsOrControllerIDsQuery(tt.ids, tt.sort)
+			actual := workbench.IDsControllerIDsOrNamesQuery(tt.ids, tt.sort)
 			require.Equal(t, tt.expected, actual)
 		})
 	}
 }
 
-func TestIDsQuery(t *testing.T) {
+func TestIDsOrNamesQuery(t *testing.T) {
 	tests := []struct {
 		name     string
 		ids      []string
+		sort     string
 		expected url.Values
+	}{
+		{
+			name:     "empty ids",
+			ids:      nil,
+			expected: url.Values{},
+		},
+		{
+			name: "all uuids",
+			ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"},
+			sort: "id.asc",
+			expected: url.Values{
+				"or": []string{
+					"(id.in.(123e4567-e89b-12d3-a456-426614174000,123e4567-e89b-12d3-a456-426614174001)," +
+						"name.in.(123e4567-e89b-12d3-a456-426614174000,123e4567-e89b-12d3-a456-426614174001))",
+				},
+				"order": []string{"id.asc"},
+			},
+		},
+		{
+			name: "all names",
+			ids:  []string{"name-a", "name-b"},
+			expected: url.Values{
+				"or": []string{"(name.in.(name-a,name-b))"},
+			},
+		},
+		{
+			name: "mixed uuid and names",
+			ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "name-a"},
+			expected: url.Values{
+				"or": []string{
+					"(id.in.(123e4567-e89b-12d3-a456-426614174000)," +
+						"name.in.(123e4567-e89b-12d3-a456-426614174000,name-a))",
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			actual := workbench.IDsOrNamesQuery(tt.ids, tt.sort)
+			require.Equal(t, tt.expected, actual)
+		})
+	}
+}
+
+func TestIDsControllerIDsOrJobNamesQuery(t *testing.T) {
+	tests := []struct {
+		name     string
+		ids      []string
 		sort     string
+		expected url.Values
 	}{
 		{
 			name:     "empty ids",
@@ -67,20 +161,42 @@ func TestIDsQuery(t *testing.T) {
 			expected: url.Values{},
 		},
 		{
-			name:     "single id",
-			ids:      []string{"id-a"},
-			expected: url.Values{"id": []string{"in.(id-a)"}},
+			name: "all uuids",
+			ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"},
+			sort: "id.asc",
+			expected: url.Values{
+				"or": []string{
+					"(id.in.(123e4567-e89b-12d3-a456-426614174000,123e4567-e89b-12d3-a456-426614174001)," +
+						"job_name.in.(123e4567-e89b-12d3-a456-426614174000,123e4567-e89b-12d3-a456-426614174001))",
+				},
+				"order": []string{"id.asc"},
+			},
 		},
 		{
-			name:     "multiple ids",
-			ids:      []string{"id-a", "id-b"},
-			expected: url.Values{"id": []string{"in.(id-a,id-b)"}},
+			name: "all controller ids / job names",
+			ids:  []string{"controller-a", "job-a"},
+			expected: url.Values{
+				"or": []string{
+					"(controller_id.in.(controller-a,job-a),job_name.in.(controller-a,job-a))",
+				},
+			},
+		},
+		{
+			name: "mixed uuid, controller id, job names",
+			ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-a", "job-a"},
+			expected: url.Values{
+				"or": []string{
+					"(id.in.(123e4567-e89b-12d3-a456-426614174000)," +
+						"controller_id.in.(controller-a,job-a)," +
+						"job_name.in.(123e4567-e89b-12d3-a456-426614174000,controller-a,job-a))",
+				},
+			},
 		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			actual := workbench.IDsQuery(tt.ids, tt.sort)
+			actual := workbench.IDsControllerIDsOrJobNamesQuery(tt.ids, tt.sort)
 			require.Equal(t, tt.expected, actual)
 		})
 	}
diff --git a/internal/services/workbench/workbench.go b/internal/services/workbench/workbench.go
index 05259db..99eddd0 100644
--- a/internal/services/workbench/workbench.go
+++ b/internal/services/workbench/workbench.go
@@ -17,6 +17,7 @@ import (
 	"github.com/sassoftware/wbctl/internal/httputil"
 	"github.com/sassoftware/wbctl/internal/i18n"
 	"github.com/sassoftware/wbctl/internal/maputil"
+	"github.com/sassoftware/wbctl/internal/sliceutil"
 	"github.com/sassoftware/wbctl/internal/types"
 )
 
@@ -127,7 +128,7 @@ func GetValidDataplaneID(ctx context.Context, svc *WorkbenchService, dataplaneID
 	return dataplanes[0].ID, nil
 }
 
-// GetValidWorkspaceRequests takes the given worksaceIDs, fetches those
+// GetValidWorkspaceRequests takes the given workspaceIDs, fetches those
 // workspaces from the server, and returns the database id and full workspace
 // request body for each.
 func GetValidWorkspaceRequests(
@@ -149,12 +150,16 @@ func GetValidWorkspaceRequests(
 				),
 			)
 		}
-		id := workspaces[0].ControllerID
+		workspace, err := sliceutil.GetAndEnsureOnlyOne(workspaces)
+		if err != nil {
+			return nil, err
+		}
+		id := workspace.ControllerID
 		if id == "" {
-			id = workspaces[0].ID
+			id = workspace.ID
 		}
-		mountName := workspaces[0].Name
-		homeDir := isHomeDir(workspaces[0])
+		mountName := workspace.Name
+		homeDir := isHomeDir(workspace)
 		if homeDir {
 			mountName = "sas"
 		}
@@ -164,7 +169,7 @@ func GetValidWorkspaceRequests(
 				MountName: mountName,
 			},
 			HomeDir: homeDir,
-			DbID:    workspaces[0].ID,
+			DbID:    workspace.ID,
 		})
 	}
 	return validWorkspaces, nil
diff --git a/internal/services/workbench/workbench_test.go b/internal/services/workbench/workbench_test.go
index 0fd8e33..6dc414d 100644
--- a/internal/services/workbench/workbench_test.go
+++ b/internal/services/workbench/workbench_test.go
@@ -10,6 +10,7 @@ import (
 	"testing"
 
 	"github.com/sassoftware/wbctl/internal/constants"
+	"github.com/sassoftware/wbctl/internal/i18n"
 	"github.com/sassoftware/wbctl/internal/postgrest"
 	"github.com/sassoftware/wbctl/internal/services/workbench"
 	"github.com/sassoftware/wbctl/internal/stubs"
@@ -378,7 +379,7 @@ func TestGetWorkspaceRequests(t *testing.T) {
 			request: request{workspaceIDs: []string{"ws-1"}},
 			mockEndpoints: getEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
-				"controller_id=in.(ws-1)",
+				"or=(controller_id.in.(ws-1),name.in.(ws-1))",
 				http.StatusOK,
 				`[ {"id":"ws-1","controller_id":"ctrl-1","name":"myws","properties":{"HOME_DIRECTORY":"true"}} ]`,
 			),
@@ -393,7 +394,7 @@ func TestGetWorkspaceRequests(t *testing.T) {
 			request: request{workspaceIDs: []string{"00000000-0000-0000-0000-000000000001"}},
 			mockEndpoints: getEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
-				"id=in.(00000000-0000-0000-0000-000000000001)",
+				"or=(id.in.(00000000-0000-0000-0000-000000000001),name.in.(00000000-0000-0000-0000-000000000001))",
 				http.StatusOK,
 				`[ {"id":"00000000-0000-0000-0000-000000000001","controller_id":"","name":"wsname","properties":{}} ]`,
 			),
@@ -408,23 +409,36 @@ func TestGetWorkspaceRequests(t *testing.T) {
 			request: request{workspaceIDs: []string{"missing"}},
 			mockEndpoints: getEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
-				"controller_id=in.(missing)",
+				"or=(controller_id.in.(missing),name.in.(missing))",
 				http.StatusOK,
 				`[]`,
 			),
 			expected: expected{err: "volume not found: missing"},
 		},
+		{
+			name:    "multiple workspaces match one name",
+			request: request{workspaceIDs: []string{"ws-name"}},
+			mockEndpoints: getEndpoints(
+				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
+				"or=(controller_id.in.(ws-name),name.in.(ws-name))",
+				http.StatusOK,
+				`[ {"id":"ws-1","controller_id":"ctrl-a","name":"ws-name","properties":{}},`+
+					`{"id":"ws-2","controller_id":"ctrl-b","name":"ws-name","properties":{}} ]`,
+			),
+			expected: expected{err: i18n.Format(i18n.MultipleElementsFound)},
+		},
 		{
 			name:    "invalid workspace response",
 			request: request{workspaceIDs: []string{"ws-1"}},
 			mockEndpoints: getEndpoints(
 				postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces),
-				"controller_id=in.(ws-1)",
+				"or=(controller_id.in.(ws-1),name.in.(ws-1))",
 				http.StatusOK,
 				`[invalid`,
 			),
 			expected: expected{
-				err: "GET ${URL}/postgrest/workspaces?controller_id=in.%28ws-1%29 failed" +
+				err: "GET ${URL}/postgrest/workspaces?" +
+					"or=%28controller_id.in.%28ws-1%29%2Cname.in.%28ws-1%29%29 failed" +
 					"\n Caused by:" +
 					"\n\t* invalid character 'i' looking for beginning of value",
 			},
diff --git a/internal/services/workbench/workbenches.go b/internal/services/workbench/workbenches.go
index 488359b..d232af2 100644
--- a/internal/services/workbench/workbenches.go
+++ b/internal/services/workbench/workbenches.go
@@ -20,27 +20,45 @@ import (
 )
 
 // GetWorkbenches gets the workbenches in the given WorkbenchService. If given
-// any ids, only the matching resources will be returned. Otherwise, all
+// any ids/names, only the matching resources will be returned. Otherwise, all
 // resources are returned.
+// nolint:dupl
 func GetWorkbenches(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	sortSpec string,
 ) (types.Workbenches, error) {
-	logutil.GetLogger().Info("getting workbenches", "ids", ids, "sort", sortSpec)
+	logutil.GetLogger().Info("getting workbenches", "args", args, "sort", sortSpec)
 	sortSpec = normalizeSortSpec(sortSpec, map[string][]string{
 		constants.SortKeyAge:   {constants.SortKeyCreatedAt},
 		constants.SortKeyState: {constants.SortKeyCurrentState},
 	})
-	return result.Split(Get[types.Workbenches](
-		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointWorkbenches), IDsOrControllerIDsQuery(ids, sortSpec),
+	workbenches, err := result.Split(Get[types.Workbenches](
+		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointWorkbenches), IDsControllerIDsOrNamesQuery(args, sortSpec),
 	))
+	if err != nil {
+		return nil, err
+	}
+	return selectByIDThenName(
+		args,
+		workbenches,
+	), nil
+}
+
+func resolveWorkbenchIDs(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	return resolveIDs(
+		ctx, s, args, GetWorkbenches, types.Workbench.GetID, constants.WorkbenchType,
+	)
 }
 
-// BulkDeleteWorkbenches deletes the workbench with the given id.
-func BulkDeleteWorkbenches(ctx context.Context, s *WorkbenchService, ids []string) ([]string, error) {
-	logutil.GetLogger().Info("deleting workbenches", "ids", ids)
+// BulkDeleteWorkbenches deletes the workbenches with the given ids/names.
+func BulkDeleteWorkbenches(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	logutil.GetLogger().Info("deleting workbenches", "args", args)
+	ids, err := resolveWorkbenchIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[[]string](
 		ctx,
 		s,
@@ -64,15 +82,19 @@ func CreateWorkbench(
 	))
 }
 
-// BulkUpdateWorkbenches  applies the given types.Workbench to all the
-// workbenches with the given ids.
+// BulkUpdateWorkbenches applies the given types.Workbench to all the
+// workbenches with the given ids/names.
 func BulkUpdateWorkbenches(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	body types.Workbench,
 ) (types.Workbenches, error) {
-	logutil.GetLogger().Info("updating workbenches", "ids", ids, "body", body)
+	logutil.GetLogger().Info("updating workbenches", "args", args, "body", body)
+	ids, err := resolveWorkbenchIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return bulkUpdateForUniqueNameConstrainedResources[types.Workbench, types.Workbenches](
 		ctx,
 		s,
diff --git a/internal/services/workbench/workbenches_test.go b/internal/services/workbench/workbenches_test.go
index 211d223..5be7103 100644
--- a/internal/services/workbench/workbenches_test.go
+++ b/internal/services/workbench/workbenches_test.go
@@ -6,7 +6,6 @@ package workbench_test
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"strings"
 	"testing"
@@ -55,6 +54,13 @@ const (
     "name": "wb-controller",
     "username": "bob",
     "application_id": "app-2"
+  },
+  {
+    "id": "wb-name",
+    "controller_id": "ctrl-name",
+    "name": "workbench-name",
+    "username": "carol",
+    "application_id": "app-3"
   }
 ]
 `
@@ -323,6 +329,9 @@ Properties:
 		"POST ${URL}/postgrest/rpc/workbench_update failed" +
 		"\n Caused by:" +
 		"\n\t* invalid character 'i' looking for beginning of value"
+	workbenchResolveIDsQuery               = "or=(controller_id.in.(wb-1,wb-2),name.in.(wb-1,wb-2))"
+	workbenchResolveIDsResponseJSON        = `[{"id":"wb-1"},{"id":"wb-2"}]`
+	workbenchDeleteSuccessResponseJSON     = `["wb-1", "wb-2"]`
 	expectedDeleteWorkbenchRequestJSON     = `{"ids":["wb-1","wb-2"]}`
 	createWorkbenchResponseJSON            = `{"id":"wb-1","name":"wb-one"}`
 	expectedBulkUpdateWorkbenchRequestJSON = `{"ids":["wb-1","wb-2"],"workbench":{"desired_state":"STOPPED"}}`
@@ -344,7 +353,7 @@ var (
 		},
 	}
 
-	expectedResponseTwoWorkbenches = types.Workbenches{
+	expectedResponseMixedWorkbenches = types.Workbenches{
 		{
 			ID:            "123e4567-e89b-12d3-a456-426614174000",
 			ControllerID:  "ctrl-uuid",
@@ -359,6 +368,13 @@ var (
 			Username:      "bob",
 			ApplicationID: "app-2",
 		},
+		{
+			ID:            "wb-name",
+			ControllerID:  "ctrl-name",
+			Name:          "workbench-name",
+			Username:      "carol",
+			ApplicationID: "app-3",
+		},
 	}
 )
 
@@ -370,9 +386,11 @@ func TestDeleteWorkbench(t *testing.T) {
 				ids: []string{"wb-1", "wb-2"},
 			},
 			mockEndpoints: getWorkbenchesDeleteEndpoints(
+				workbenchResolveIDsQuery,
+				workbenchResolveIDsResponseJSON,
 				expectedDeleteWorkbenchRequestJSON,
 				http.StatusOK,
-				`["wb-1", "wb-2"]`,
+				workbenchDeleteSuccessResponseJSON,
 			),
 			expected: deleteExpected{
 				result: []string{"wb-1", "wb-2"},
@@ -384,16 +402,37 @@ func TestDeleteWorkbench(t *testing.T) {
 				ids: []string{"wb-1", "wb-2"},
 			},
 			mockEndpoints: getWorkbenchesDeleteEndpoints(
+				workbenchResolveIDsQuery,
+				workbenchResolveIDsResponseJSON,
 				expectedDeleteWorkbenchRequestJSON,
 				http.StatusUnauthorized,
 				"unauthorized",
 			),
 			expected: deleteExpected{
-				err: "POST %s/postgrest/rpc/workbench_bulk_delete failed\n" +
+				err: "POST ${URL}/postgrest/rpc/workbench_bulk_delete failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
 		},
+		{
+			name: "delete workbench not found",
+			request: deleteRequest{
+				ids: []string{"missing"},
+			},
+			mockEndpoints: getWorkbenchesDeleteEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: deleteExpected{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.WorkbenchType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -403,7 +442,7 @@ func TestDeleteWorkbench(t *testing.T) {
 			svc := workbench.NewWorkbenchService(srv.URL, "access-token", "csrf-token")
 			actual, err := workbench.BulkDeleteWorkbenches(context.Background(), svc, tt.request.ids)
 			if tt.expected.err != "" {
-				require.EqualError(t, err, fmt.Sprintf(tt.expected.err, srv.URL))
+				require.EqualError(t, err, strings.ReplaceAll(tt.expected.err, "${URL}", srv.URL))
 				return
 			}
 			require.NoError(t, err)
@@ -450,7 +489,7 @@ func TestCreateWorkbench(t *testing.T) {
 				"unauthorized",
 			),
 			expected: createExpected[types.Workbench]{
-				err: "POST %s/postgrest/rpc/workbench_create failed\n" +
+				err: "POST ${URL}/postgrest/rpc/workbench_create failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
@@ -465,7 +504,7 @@ func TestCreateWorkbench(t *testing.T) {
 
 			actual, err := workbench.CreateWorkbench(context.Background(), svc, tt.request.body)
 			if tt.expected.err != "" {
-				require.EqualError(t, err, fmt.Sprintf(tt.expected.err, srv.URL))
+				require.EqualError(t, err, strings.ReplaceAll(tt.expected.err, "${URL}", srv.URL))
 				return
 			}
 			require.NoError(t, err)
@@ -483,6 +522,8 @@ func TestBulkUpdateWorkbenches(t *testing.T) {
 				body: types.Workbench{DesiredState: "STOPPED"},
 			},
 			mockEndpoints: getWorkbenchesBulkUpdateEndpoints(
+				workbenchResolveIDsQuery,
+				workbenchResolveIDsResponseJSON,
 				expectedBulkUpdateWorkbenchRequestJSON,
 				http.StatusOK,
 				workbenchesBulkUpdateResponseJSON,
@@ -501,6 +542,8 @@ func TestBulkUpdateWorkbenches(t *testing.T) {
 				body: types.Workbench{DesiredState: "STOPPED"},
 			},
 			mockEndpoints: getWorkbenchesBulkUpdateEndpoints(
+				workbenchResolveIDsQuery,
+				workbenchResolveIDsResponseJSON,
 				expectedBulkUpdateWorkbenchRequestJSON,
 				http.StatusUnauthorized,
 				"unauthorized",
@@ -518,6 +561,8 @@ func TestBulkUpdateWorkbenches(t *testing.T) {
 				body: types.Workbench{DesiredState: "STOPPED"},
 			},
 			mockEndpoints: getWorkbenchesBulkUpdateEndpoints(
+				workbenchResolveIDsQuery,
+				workbenchResolveIDsResponseJSON,
 				expectedBulkUpdateWorkbenchRequestJSON,
 				http.StatusOK,
 				"[",
@@ -535,6 +580,8 @@ func TestBulkUpdateWorkbenches(t *testing.T) {
 				body: types.Workbench{Name: "wb-one-renamed"},
 			},
 			mockEndpoints: getWorkbenchesUpdateEndpoints(
+				`or=(controller_id.in.(wb-1),name.in.(wb-1))`,
+				`[{"id":"wb-1"}]`,
 				expectedUpdateWorkbenchRequest2JSON,
 				http.StatusOK,
 				`{"id":"wb-1","name":"wb-one-renamed"}`,
@@ -549,11 +596,38 @@ func TestBulkUpdateWorkbenches(t *testing.T) {
 				ids:  []string{"wb-1", "wb-2"},
 				body: types.Workbench{Name: "wb-one-renamed"},
 			},
+			mockEndpoints: getWorkbenchesBulkUpdateEndpoints(
+				workbenchResolveIDsQuery,
+				workbenchResolveIDsResponseJSON,
+				"",
+				http.StatusOK,
+				"",
+			),
 			expected: bulkupdateExpected[types.Workbenches]{
 				err: i18n.Format(i18n.BulkUpdateNameError,
 					i18n.A{constants.I18nArgKeyType: constants.WorkbenchesType}),
 			},
 		},
+		{
+			name: "bulk update workbenches with no matching resources returns not found",
+			request: bulkupdateRequest[types.Workbench]{
+				ids:  []string{"missing"},
+				body: types.Workbench{DesiredState: "STOPPED"},
+			},
+			mockEndpoints: getWorkbenchesBulkUpdateEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: bulkupdateExpected[types.Workbenches]{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.WorkbenchType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 		{
 			name: "update single workbench name invalid response",
 			request: bulkupdateRequest[types.Workbench]{
@@ -561,6 +635,8 @@ func TestBulkUpdateWorkbenches(t *testing.T) {
 				body: types.Workbench{Name: "UPDATED"},
 			},
 			mockEndpoints: getWorkbenchesUpdateEndpoints(
+				`or=(controller_id.in.(wb-1),name.in.(wb-1))`,
+				`[{"id":"wb-1"}]`,
 				expectedUpdateWorkbenchRequestJSON,
 				http.StatusOK,
 				"[invalid",
@@ -602,19 +678,20 @@ func TestGetWorkbenches(t *testing.T) {
 			},
 		},
 		{
-			name: "get workbench success with mixed uuid and controller ids query",
+			name: "get workbench success with mixed uuid, controller ids, names query",
 			request: getRequest{
-				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-wb-2"},
+				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-wb-2", "workbench-name"},
 				sort: "id.asc",
 			},
 			mockEndpoints: getWorkbenchesGetEndpoints(
 				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),"+
-					"controller_id.in.(controller-wb-2))&order=id.asc",
+					"controller_id.in.(controller-wb-2,workbench-name),"+
+					"name.in.(123e4567-e89b-12d3-a456-426614174000,controller-wb-2,workbench-name))&order=id.asc",
 				http.StatusOK,
 				workbenchesGetMixedIDsResponseJSON,
 			),
 			expected: getExpected[types.Workbenches]{
-				result: expectedResponseTwoWorkbenches,
+				result: expectedResponseMixedWorkbenches,
 			},
 		},
 		{
@@ -763,12 +840,12 @@ func TestGetWorkbenches(t *testing.T) {
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getWorkbenchesGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),name.in.(id-a))",
 				http.StatusOK,
 				"[",
 			),
 			expected: getExpected[types.Workbenches]{
-				err: "GET ${URL}/postgrest/workbenches?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/workbenches?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* unexpected EOF",
 			},
@@ -779,12 +856,12 @@ func TestGetWorkbenches(t *testing.T) {
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getWorkbenchesGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),name.in.(id-a))",
 				http.StatusUnauthorized,
 				"unauthorized",
 			),
 			expected: getExpected[types.Workbenches]{
-				err: "GET ${URL}/postgrest/workbenches?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/workbenches?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
@@ -922,12 +999,14 @@ func TestDescribeWorkbenches(t *testing.T) {
 }
 
 func getWorkbenchesDeleteEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getWorkbenchesGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkbenchBulkDelete),
@@ -938,8 +1017,7 @@ func getWorkbenchesDeleteEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getWorkbenchesCreateEndpoints(
@@ -964,12 +1042,14 @@ func getWorkbenchesCreateEndpoints(
 }
 
 func getWorkbenchesBulkUpdateEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getWorkbenchesGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkbenchBulkUpdate),
@@ -980,17 +1060,18 @@ func getWorkbenchesBulkUpdateEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getWorkbenchesUpdateEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getWorkbenchesGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkbenchUpdate),
@@ -1001,8 +1082,7 @@ func getWorkbenchesUpdateEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getWorkbenchesGetEndpoints(
diff --git a/internal/services/workbench/workspaces.go b/internal/services/workbench/workspaces.go
index ad46b7e..4941448 100644
--- a/internal/services/workbench/workspaces.go
+++ b/internal/services/workbench/workspaces.go
@@ -21,29 +21,47 @@ import (
 )
 
 // GetWorkspaces gets the workspaces in the given WorkbenchService. If given any
-// ids, only the matching resources will be returned. Otherwise, all resources
+// ids/names, only the matching resources will be returned. Otherwise, all resources
 // are returned.
+// nolint:dupl
 func GetWorkspaces(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	sortSpec string,
 ) (types.Workspaces, error) {
-	logutil.GetLogger().Info("getting volumes", "ids", ids, "sort", sortSpec)
+	logutil.GetLogger().Info("getting volumes", "args", args, "sort", sortSpec)
 	sortSpec = normalizeSortSpec(sortSpec, map[string][]string{
 		constants.SortKeyAge:              {constants.SortKeyCreatedAt},
 		constants.SortKeySize:             {constants.SortKeyStorageSizeG},
 		constants.SortKeyHomeDir:          {constants.SortKeyPropertiesHomeDirectory},
 		constants.SortKeyStorageClassName: {constants.SortKeyStorageSpecStorageClassName},
 	})
-	return result.Split(Get[types.Workspaces](
-		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces), IDsOrControllerIDsQuery(ids, sortSpec),
+	workspaces, err := result.Split(Get[types.Workspaces](
+		ctx, s, postgrest.Endpoint(constants.WorkbenchEndpointWorkspaces), IDsControllerIDsOrNamesQuery(args, sortSpec),
 	))
+	if err != nil {
+		return nil, err
+	}
+	return selectByIDThenName(
+		args,
+		workspaces,
+	), nil
+}
+
+func resolveWorkspaceIDs(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	return resolveIDs(
+		ctx, s, args, GetWorkspaces, types.Workspace.GetID, constants.VolumeType,
+	)
 }
 
-// BulkDeleteWorkspaces deletes the workspaces with the given ids.
-func BulkDeleteWorkspaces(ctx context.Context, s *WorkbenchService, ids []string) ([]string, error) {
-	logutil.GetLogger().Info("deleting volumes", "ids", ids)
+// BulkDeleteWorkspaces deletes the workspaces with the given ids/names.
+func BulkDeleteWorkspaces(ctx context.Context, s *WorkbenchService, args []string) ([]string, error) {
+	logutil.GetLogger().Info("deleting volumes", "args", args)
+	ids, err := resolveWorkspaceIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return result.Split(Post[[]string](
 		ctx,
 		s,
@@ -67,15 +85,19 @@ func CreateWorkspace(
 	))
 }
 
-// BulkUpdateWorkspaces  applies the given types.Workspace to all the workspaces
-// with the given ids.
+// BulkUpdateWorkspaces applies the given types.Workspace to all the
+// workspaces with the given ids/names.
 func BulkUpdateWorkspaces(
 	ctx context.Context,
 	s *WorkbenchService,
-	ids []string,
+	args []string,
 	body types.Workspace,
 ) (types.Workspaces, error) {
-	logutil.GetLogger().Info("updating volumes", "ids", ids)
+	logutil.GetLogger().Info("updating volumes", "args", args)
+	ids, err := resolveWorkspaceIDs(ctx, s, args)
+	if err != nil {
+		return nil, err
+	}
 	return bulkUpdateForUniqueNameConstrainedResources[types.Workspace, types.Workspaces](
 		ctx,
 		s,
diff --git a/internal/services/workbench/workspaces_test.go b/internal/services/workbench/workspaces_test.go
index 1f53a1d..b0490fe 100644
--- a/internal/services/workbench/workspaces_test.go
+++ b/internal/services/workbench/workspaces_test.go
@@ -50,6 +50,12 @@ const (
     "controller_id": "controller-ws-2",
     "name": "workspace-controller",
     "username": "bob"
+  },
+  {
+    "id": "ws-name",
+    "controller_id": "ctrl-name",
+    "name": "workspace-name",
+    "username": "carol"
   }
 ]
 `
@@ -299,6 +305,9 @@ Properties:
   }
 ]
 `
+	workspaceResolveIDsQuery               = "or=(controller_id.in.(ws-1,ws-2),name.in.(ws-1,ws-2))"
+	workspaceResolveIDsResponseJSON        = `[{"id":"ws-1"},{"id":"ws-2"}]`
+	workspaceDeleteSuccessResponseJSON     = `["ws-1", "ws-2"]`
 	expectedDeleteWorkspaceRequestJSON     = `{"ids":["ws-1", "ws-2"]}`
 	expectedBulkUpdateWorkspaceRequestJSON = `{"ids":["ws-1","ws-2"],"workspace":` +
 		`{"description":"updated-description","pvc_status":{"status":{}}}}`
@@ -333,6 +342,12 @@ var (
 			Name:         "workspace-controller",
 			Username:     "bob",
 		},
+		{
+			ID:           "ws-name",
+			ControllerID: "ctrl-name",
+			Name:         "workspace-name",
+			Username:     "carol",
+		},
 	}
 )
 
@@ -344,9 +359,11 @@ func TestDeleteWorkspace(t *testing.T) {
 				ids: []string{"ws-1", "ws-2"},
 			},
 			mockEndpoints: getWorkspaceDeleteEndpoints(
+				workspaceResolveIDsQuery,
+				workspaceResolveIDsResponseJSON,
 				expectedDeleteWorkspaceRequestJSON,
 				http.StatusOK,
-				`["ws-1", "ws-2"]`,
+				workspaceDeleteSuccessResponseJSON,
 			),
 			expected: deleteExpected{
 				result: []string{"ws-1", "ws-2"},
@@ -358,6 +375,8 @@ func TestDeleteWorkspace(t *testing.T) {
 				ids: []string{"ws-1", "ws-2"},
 			},
 			mockEndpoints: getWorkspaceDeleteEndpoints(
+				workspaceResolveIDsQuery,
+				workspaceResolveIDsResponseJSON,
 				expectedDeleteWorkspaceRequestJSON,
 				http.StatusUnauthorized,
 				`unauthorized`,
@@ -368,6 +387,25 @@ func TestDeleteWorkspace(t *testing.T) {
 					"\t* 401 unauthorized",
 			},
 		},
+		{
+			name: "delete workspace not found",
+			request: deleteRequest{
+				ids: []string{"missing"},
+			},
+			mockEndpoints: getWorkspaceDeleteEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: deleteExpected{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.VolumeType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 
 	for _, tt := range tests {
@@ -459,6 +497,8 @@ func TestBulkUpdateWorkspaces(t *testing.T) {
 				body: types.Workspace{Description: "updated-description"},
 			},
 			mockEndpoints: getWorkspaceBulkUpdateEndpoints(
+				workspaceResolveIDsQuery,
+				workspaceResolveIDsResponseJSON,
 				expectedBulkUpdateWorkspaceRequestJSON,
 				http.StatusOK,
 				workspacesBulkUpdateResponseJSON,
@@ -477,6 +517,8 @@ func TestBulkUpdateWorkspaces(t *testing.T) {
 				body: types.Workspace{Description: "updated-description"},
 			},
 			mockEndpoints: getWorkspaceBulkUpdateEndpoints(
+				workspaceResolveIDsQuery,
+				workspaceResolveIDsResponseJSON,
 				expectedBulkUpdateWorkspaceRequestJSON,
 				http.StatusUnauthorized,
 				`unauthorized`,
@@ -494,6 +536,8 @@ func TestBulkUpdateWorkspaces(t *testing.T) {
 				body: types.Workspace{Description: "updated-description"},
 			},
 			mockEndpoints: getWorkspaceBulkUpdateEndpoints(
+				workspaceResolveIDsQuery,
+				workspaceResolveIDsResponseJSON,
 				expectedBulkUpdateWorkspaceRequestJSON,
 				http.StatusOK,
 				`[`,
@@ -511,6 +555,8 @@ func TestBulkUpdateWorkspaces(t *testing.T) {
 				body: types.Workspace{Name: "workspace-one-renamed"},
 			},
 			mockEndpoints: getWorkspaceUpdateEndpoints(
+				`or=(controller_id.in.(ws-1),name.in.(ws-1))`,
+				`[{"id":"ws-1"}]`,
 				expectedUpdateWorkspaceRequestJSON,
 				http.StatusOK,
 				createWorkspaceRenamedResponseJSON,
@@ -525,11 +571,38 @@ func TestBulkUpdateWorkspaces(t *testing.T) {
 				ids:  []string{"ws-1", "ws-2"},
 				body: types.Workspace{Name: "workspace-one-renamed"},
 			},
+			mockEndpoints: getWorkspaceBulkUpdateEndpoints(
+				workspaceResolveIDsQuery,
+				workspaceResolveIDsResponseJSON,
+				"",
+				http.StatusOK,
+				"",
+			),
 			expected: bulkupdateExpected[types.Workspaces]{
 				err: i18n.Format(i18n.BulkUpdateNameError,
 					i18n.A{constants.I18nArgKeyType: constants.VolumesType}),
 			},
 		},
+		{
+			name: "bulk update workspaces with no matching resources returns not found",
+			request: bulkupdateRequest[types.Workspace]{
+				ids:  []string{"missing"},
+				body: types.Workspace{Description: "updated-description"},
+			},
+			mockEndpoints: getWorkspaceBulkUpdateEndpoints(
+				`or=(controller_id.in.(missing),name.in.(missing))`,
+				`[]`,
+				"",
+				http.StatusOK,
+				"",
+			),
+			expected: bulkupdateExpected[types.Workspaces]{
+				err: i18n.Format(i18n.ResourceNotFoundWithID, i18n.A{
+					constants.I18nArgTypeKey: constants.VolumeType,
+					constants.I18nArgKeyID:   "missing",
+				}),
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -564,14 +637,15 @@ func TestGetWorkspaces(t *testing.T) {
 			},
 		},
 		{
-			name: "get workspaces success with mixed uuid and controller ids query",
+			name: "get workspaces success with mixed uuid, controller ids, names query",
 			request: getRequest{
-				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-ws-2"},
+				ids:  []string{"123e4567-e89b-12d3-a456-426614174000", "controller-ws-2", "ws-name"},
 				sort: "id.asc",
 			},
 			mockEndpoints: getWorkspaceGetEndpoints(
 				"or=(id.in.(123e4567-e89b-12d3-a456-426614174000),"+
-					"controller_id.in.(controller-ws-2))&order=id.asc",
+					"controller_id.in.(controller-ws-2,ws-name),"+
+					"name.in.(123e4567-e89b-12d3-a456-426614174000,controller-ws-2,ws-name))&order=id.asc",
 				http.StatusOK,
 				workspacesGetMixedIDsResponseJSON,
 			),
@@ -725,12 +799,12 @@ func TestGetWorkspaces(t *testing.T) {
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getWorkspaceGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),name.in.(id-a))",
 				http.StatusOK,
 				`[`,
 			),
 			expected: getExpected[types.Workspaces]{
-				err: "GET ${URL}/postgrest/workspaces?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/workspaces?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* unexpected EOF",
 			},
@@ -741,12 +815,12 @@ func TestGetWorkspaces(t *testing.T) {
 				ids: []string{"id-a"},
 			},
 			mockEndpoints: getWorkspaceGetEndpoints(
-				"controller_id=in.(id-a)",
+				"or=(controller_id.in.(id-a),name.in.(id-a))",
 				http.StatusUnauthorized,
 				`unauthorized`,
 			),
 			expected: getExpected[types.Workspaces]{
-				err: "GET ${URL}/postgrest/workspaces?controller_id=in.%28id-a%29 failed\n" +
+				err: "GET ${URL}/postgrest/workspaces?or=%28controller_id.in.%28id-a%29%2Cname.in.%28id-a%29%29 failed\n" +
 					" Caused by:\n" +
 					"\t* 401 unauthorized",
 			},
@@ -892,12 +966,14 @@ func TestDescribeWorkspaces(t *testing.T) {
 }
 
 func getWorkspaceUpdateEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getWorkspaceGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkspaceUpdate),
@@ -908,17 +984,18 @@ func getWorkspaceUpdateEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getWorkspaceBulkUpdateEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getWorkspaceGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkspaceBulkUpdate),
@@ -929,17 +1006,18 @@ func getWorkspaceBulkUpdateEndpoints(
 				Status: responseStatus,
 				Body:   responseBody,
 			},
-		},
-	}
+		})
 }
 
 func getWorkspaceDeleteEndpoints(
+	expectedQuery string,
+	resolveResponseBody string,
 	expectedBody string,
 	responseStatus int,
 	responseBody string,
 ) []stubs.StubHTTPEndpoint {
-	return []stubs.StubHTTPEndpoint{
-		{
+	return append(getWorkspaceGetEndpoints(expectedQuery, http.StatusOK, resolveResponseBody),
+		stubs.StubHTTPEndpoint{
 			Request: stubs.StubHTTPRequest{
 				Method:          http.MethodPost,
 				URL:             postgrest.Endpoint(constants.WorkbenchEndpointRPCWorkspaceBulkDelete),
@@ -950,8 +1028,7 @@ func getWorkspaceDeleteEndpoints(
 				Body:   responseBody,
 				Status: responseStatus,
 			},
-		},
-	}
+		})
 }
 
 func getWorkspaceCreateEndpoints(
diff --git a/internal/stubs/common_endpoints.go b/internal/stubs/common_endpoints.go
index 2e0fa89..372cf5e 100644
--- a/internal/stubs/common_endpoints.go
+++ b/internal/stubs/common_endpoints.go
@@ -12,8 +12,8 @@ import (
 )
 
 const (
-	expectedWorkspaceQueryHome        = "controller_id=in.(ws-home)"
-	expectedWorkspaceQueryData        = "controller_id=in.(ws-data)"
+	expectedWorkspaceQueryHome        = "or=(controller_id.in.(ws-home),name.in.(ws-home))"
+	expectedWorkspaceQueryData        = "or=(controller_id.in.(ws-data),name.in.(ws-data))"
 	expectedWorkspaceResponseBodyHome = `[{"id":"ws-home","controller_id":"ctrl-home","name":"home",` +
 		`"properties":{"HOME_DIRECTORY":"true"}}]`
 	expectedWorkspaceResponseBodyData = `[{"id":"ws-data","controller_id":"ctrl-data","name":"data",` +
diff --git a/internal/types/types.go b/internal/types/types.go
index f670999..b05a6b9 100644
--- a/internal/types/types.go
+++ b/internal/types/types.go
@@ -13,6 +13,14 @@ import (
 	corev1 "k8s.io/api/core/v1"
 )
 
+// IdentifiableResource is implemented by resources that can be identified by
+// an ID, (optionally) a controller ID, and a name.
+type IdentifiableResource interface {
+	GetID() string
+	GetControllerID() string
+	GetName() string
+}
+
 // Config holds CLI configuration values.
 type Config struct {
 	Endpoint string `json:"endpoint,omitempty"`
@@ -93,6 +101,24 @@ type Job struct {
 	Mounts                    []MountRequest         `json:"mounts,omitempty" wbctl:"alias:volumes,editable"`
 }
 
+// GetID returns the ID of the job.
+func (r Job) GetID() string {
+	return r.ID
+}
+
+// GetControllerID returns the controller ID of the job. Jobs do not have a
+// controller ID, so this always returns an empty string.
+func (r Job) GetControllerID() string {
+	return ""
+}
+
+// GetName returns the name of the job.
+func (r Job) GetName() string {
+	return r.Name
+}
+
+var _ IdentifiableResource = (*Job)(nil)
+
 // Jobs is a slice of Job values.
 type Jobs []Job
 
@@ -131,6 +157,23 @@ type JobSubmission struct {
 	LogURL       string                      `json:"log_url,omitempty"`
 }
 
+// GetID returns the ID of the job submission.
+func (r JobSubmission) GetID() string {
+	return r.ID
+}
+
+// GetControllerID returns the controller ID of the job submission.
+func (r JobSubmission) GetControllerID() string {
+	return r.ControllerID
+}
+
+// GetName returns the job name of the job submission.
+func (r JobSubmission) GetName() string {
+	return r.JobName
+}
+
+var _ IdentifiableResource = (*JobSubmission)(nil)
+
 // JobSubmissions is a slice of JobSubmission values.
 type JobSubmissions []JobSubmission
 
@@ -186,6 +229,23 @@ type Workspace struct {
 	Username     string                            `json:"username,omitempty"`
 }
 
+// GetID returns the ID of the workspace.
+func (r Workspace) GetID() string {
+	return r.ID
+}
+
+// GetControllerID returns the controller ID of the workspace.
+func (r Workspace) GetControllerID() string {
+	return r.ControllerID
+}
+
+// GetName returns the name of the workspace.
+func (r Workspace) GetName() string {
+	return r.Name
+}
+
+var _ IdentifiableResource = (*Workspace)(nil)
+
 // Workspaces is a slice of Workspace values.
 type Workspaces []Workspace
 
@@ -223,6 +283,23 @@ type Workbench struct {
 	Mounts         []MountRequest              `json:"mounts,omitempty" wbctl:"alias:volumes,editable"`
 }
 
+// GetID returns the ID of the workbench.
+func (r Workbench) GetID() string {
+	return r.ID
+}
+
+// GetControllerID returns the controller ID of the workbench.
+func (r Workbench) GetControllerID() string {
+	return r.ControllerID
+}
+
+// GetName returns the name of the workbench.
+func (r Workbench) GetName() string {
+	return r.Name
+}
+
+var _ IdentifiableResource = (*Workbench)(nil)
+
 // Workbenches is a slice of Workbench values.
 type Workbenches []Workbench
 
@@ -312,6 +389,23 @@ type ImageBuild struct {
 	PodStatus    []ExecuteableResourceStatus `json:"pod_status,omitempty"`
 }
 
+// GetID returns the ID of the image build.
+func (r ImageBuild) GetID() string {
+	return r.ID
+}
+
+// GetControllerID returns the controller ID of the image build.
+func (r ImageBuild) GetControllerID() string {
+	return r.ControllerID
+}
+
+// GetName returns the name of the image build.
+func (r ImageBuild) GetName() string {
+	return r.Name
+}
+
+var _ IdentifiableResource = (*ImageBuild)(nil)
+
 // ImageBuilds is a slice of ImageBuild values.
 type ImageBuilds []ImageBuild
 
@@ -348,6 +442,23 @@ type Image struct {
 	Username                     string                      `json:"username,omitempty"`
 }
 
+// GetID returns the ID of the image.
+func (r Image) GetID() string {
+	return r.ID
+}
+
+// GetControllerID returns the controller ID of the image.
+func (r Image) GetControllerID() string {
+	return r.ControllerID
+}
+
+// GetName returns the name of the image.
+func (r Image) GetName() string {
+	return r.Name
+}
+
+var _ IdentifiableResource = (*Image)(nil)
+
 // Images is a slice of Image values.
 type Images []Image
 
@@ -417,6 +528,23 @@ type Snapshot struct {
 	WorkspaceSource *Workspace                    `json:"workspace_source,omitempty" wbctl:"alias:volume_source"`
 }
 
+// GetID returns the ID of the snapshot.
+func (r Snapshot) GetID() string {
+	return r.ID
+}
+
+// GetControllerID returns the controller ID of the snapshot.
+func (r Snapshot) GetControllerID() string {
+	return r.ControllerID
+}
+
+// GetName returns the name of the snapshot.
+func (r Snapshot) GetName() string {
+	return r.Name
+}
+
+var _ IdentifiableResource = (*Snapshot)(nil)
+
 // VolumeStatus describes a snapshot volume status.
 type VolumeStatus struct {
 	Name    string                         `json:"name,omitempty"`
diff --git a/internal/types/types_test.go b/internal/types/types_test.go
index 7bd7cac..72d3fbf 100644
--- a/internal/types/types_test.go
+++ b/internal/types/types_test.go
@@ -470,6 +470,108 @@ func TestAsWorkspaceSlice(t *testing.T) {
 	}
 }
 
+func TestIdentifiableResourceGetters(t *testing.T) {
+	cases := []struct {
+		name                 string
+		resource             types.IdentifiableResource
+		expectedID           string
+		expectedControllerID string
+		expectedName         string
+	}{
+		{
+			name:                 "zero value job returns empty strings",
+			resource:             types.Job{},
+			expectedID:           "",
+			expectedControllerID: "",
+			expectedName:         "",
+		},
+		{
+			name: "image build returns ID, controller ID, and name",
+			resource: types.ImageBuild{
+				ID:           "image-build-1",
+				ControllerID: "controller-1",
+				Name:         "image-build-name",
+			},
+			expectedID:           "image-build-1",
+			expectedControllerID: "controller-1",
+			expectedName:         "image-build-name",
+		},
+		{
+			name: "image returns ID, controller ID, and name",
+			resource: types.Image{
+				ID:           "image-1",
+				ControllerID: "controller-1",
+				Name:         "image-name",
+			},
+			expectedID:           "image-1",
+			expectedControllerID: "controller-1",
+			expectedName:         "image-name",
+		},
+		{
+			name: "job submission returns job name as name",
+			resource: types.JobSubmission{
+				ID:           "job-submission-1",
+				ControllerID: "controller-1",
+				JobName:      "job-name",
+			},
+			expectedID:           "job-submission-1",
+			expectedControllerID: "controller-1",
+			expectedName:         "job-name",
+		},
+		{
+			name: "job returns ID and name with empty controller ID",
+			resource: types.Job{
+				ID:   "job-1",
+				Name: "job-name",
+			},
+			expectedID:           "job-1",
+			expectedControllerID: "",
+			expectedName:         "job-name",
+		},
+		{
+			name: "snapshot returns ID, controller ID, and name",
+			resource: types.Snapshot{
+				ID:           "snapshot-1",
+				ControllerID: "controller-1",
+				Name:         "snapshot-name",
+			},
+			expectedID:           "snapshot-1",
+			expectedControllerID: "controller-1",
+			expectedName:         "snapshot-name",
+		},
+		{
+			name: "workspace returns ID, controller ID, and name",
+			resource: types.Workspace{
+				ID:           "workspace-1",
+				ControllerID: "controller-1",
+				Name:         "workspace-name",
+			},
+			expectedID:           "workspace-1",
+			expectedControllerID: "controller-1",
+			expectedName:         "workspace-name",
+		},
+		{
+			name: "workbench returns ID, controller ID, and name",
+			resource: types.Workbench{
+				ID:           "workbench-1",
+				ControllerID: "controller-1",
+				Name:         "workbench-name",
+			},
+			expectedID:           "workbench-1",
+			expectedControllerID: "controller-1",
+			expectedName:         "workbench-name",
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			require.Equal(t, tc.expectedID, tc.resource.GetID())
+			require.Equal(t, tc.expectedControllerID, tc.resource.GetControllerID())
+			require.Equal(t, tc.expectedName, tc.resource.GetName())
+		})
+	}
+}
+
 func TestGetFunction(t *testing.T) {
 	metadata := []types.FunctionMetadata[string]{
 		{