diff --git a/models/actions/run_job.go b/models/actions/run_job.go
index de4b6aab66..9f8edfe4fc 100644
--- a/models/actions/run_job.go
+++ b/models/actions/run_job.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/timeutil"
 	"code.gitea.io/gitea/modules/util"
 
@@ -71,6 +72,15 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
 	return job.Run.LoadAttributes(ctx)
 }
 
+func (job *ActionRunJob) ItRunsOn(labels []string) bool {
+	if len(labels) == 0 || len(job.RunsOn) == 0 {
+		return false
+	}
+	labelSet := make(container.Set[string])
+	labelSet.AddMultiple(labels...)
+	return labelSet.IsSubset(job.RunsOn)
+}
+
 func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
 	var job ActionRunJob
 	has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)
diff --git a/models/actions/run_job_test.go b/models/actions/run_job_test.go
new file mode 100644
index 0000000000..50a4ba10d8
--- /dev/null
+++ b/models/actions/run_job_test.go
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT
+
+package actions
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestActionRunJob_ItRunsOn(t *testing.T) {
+	actionJob := ActionRunJob{RunsOn: []string{"ubuntu"}}
+	agentLabels := []string{"ubuntu", "node-20"}
+
+	assert.True(t, actionJob.ItRunsOn(agentLabels))
+	assert.False(t, actionJob.ItRunsOn([]string{}))
+
+	actionJob.RunsOn = append(actionJob.RunsOn, "node-20")
+
+	assert.True(t, actionJob.ItRunsOn(agentLabels))
+
+	agentLabels = []string{"ubuntu"}
+
+	assert.False(t, actionJob.ItRunsOn(agentLabels))
+
+	actionJob.RunsOn = []string{}
+
+	assert.False(t, actionJob.ItRunsOn(agentLabels))
+}
diff --git a/models/actions/task.go b/models/actions/task.go
index 8bd139a2d6..31655d2f1d 100644
--- a/models/actions/task.go
+++ b/models/actions/task.go
@@ -12,7 +12,6 @@ import (
 	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unit"
-	"code.gitea.io/gitea/modules/container"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/timeutil"
@@ -245,7 +244,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
 	var job *ActionRunJob
 	log.Trace("runner labels: %v", runner.AgentLabels)
 	for _, v := range jobs {
-		if isSubset(runner.AgentLabels, v.RunsOn) {
+		if v.ItRunsOn(runner.AgentLabels) {
 			job = v
 			break
 		}
@@ -482,20 +481,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
 		Find(&tasks)
 }
 
-func isSubset(set, subset []string) bool {
-	m := make(container.Set[string], len(set))
-	for _, v := range set {
-		m.Add(v)
-	}
-
-	for _, v := range subset {
-		if !m.Contains(v) {
-			return false
-		}
-	}
-	return true
-}
-
 func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
 	if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
 		return timeutil.TimeStamp(0)
diff --git a/models/actions/task_list.go b/models/actions/task_list.go
index df4b43c5ef..502d29e1a3 100644
--- a/models/actions/task_list.go
+++ b/models/actions/task_list.go
@@ -50,7 +50,7 @@ type FindTaskOptions struct {
 	RepoID        int64
 	OwnerID       int64
 	CommitSHA     string
-	Status        Status
+	Status        []Status
 	UpdatedBefore timeutil.TimeStamp
 	StartedBefore timeutil.TimeStamp
 	RunnerID      int64
@@ -67,8 +67,8 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
 	if opts.CommitSHA != "" {
 		cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
 	}
-	if opts.Status > StatusUnknown {
-		cond = cond.And(builder.Eq{"status": opts.Status})
+	if opts.Status != nil {
+		cond = cond.And(builder.In("status", opts.Status))
 	}
 	if opts.UpdatedBefore > 0 {
 		cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
diff --git a/models/fixtures/action_run_job.yml b/models/fixtures/action_run_job.yml
index 117bb5ea05..702c6bc832 100644
--- a/models/fixtures/action_run_job.yml
+++ b/models/fixtures/action_run_job.yml
@@ -83,3 +83,48 @@
   status: 1
   started: 1683636528
   stopped: 1683636626
+-
+  id: 393
+  run_id: 891
+  repo_id: 1
+  owner_id: 1
+  commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
+  is_fork_pull_request: 0
+  name: job_2
+  attempt: 1
+  job_id: job_2
+  task_id: 47
+  status: 5
+  runs_on: '["ubuntu-latest"]'
+  started: 1683636528
+  stopped: 1683636626
+-
+  id: 394
+  run_id: 891
+  repo_id: 1
+  owner_id: 2
+  commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
+  is_fork_pull_request: 0
+  name: job_2
+  attempt: 1
+  job_id: job_2
+  task_id: 47
+  status: 5
+  runs_on: '["debian-latest"]'
+  started: 1683636528
+  stopped: 1683636626
+-
+  id: 395
+  run_id: 891
+  repo_id: 1
+  owner_id: 3
+  commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
+  is_fork_pull_request: 0
+  name: job_2
+  attempt: 1
+  job_id: job_2
+  task_id: 47
+  status: 5
+  runs_on: '["fedora"]'
+  started: 1683636528
+  stopped: 1683636626
diff --git a/modules/container/set.go b/modules/container/set.go
index 15779983fd..2d654d0aee 100644
--- a/modules/container/set.go
+++ b/modules/container/set.go
@@ -29,6 +29,15 @@ func (s Set[T]) AddMultiple(values ...T) {
 	}
 }
 
+func (s Set[T]) IsSubset(subset []T) bool {
+	for _, v := range subset {
+		if !s.Contains(v) {
+			return false
+		}
+	}
+	return true
+}
+
 // Contains determines whether a set contains the specified element.
 // Returns true if the set contains the specified element; otherwise, false.
 func (s Set[T]) Contains(value T) bool {
diff --git a/modules/container/set_test.go b/modules/container/set_test.go
index 1502236034..3cfbf7cc2c 100644
--- a/modules/container/set_test.go
+++ b/modules/container/set_test.go
@@ -33,4 +33,9 @@ func TestSet(t *testing.T) {
 	assert.False(t, s.Contains("key1"))
 	assert.True(t, s.Contains("key6"))
 	assert.True(t, s.Contains("key7"))
+
+	assert.True(t, s.IsSubset([]string{"key6", "key7"}))
+	assert.False(t, s.IsSubset([]string{"key1"}))
+
+	assert.True(t, s.IsSubset([]string{}))
 }
diff --git a/modules/structs/action.go b/modules/structs/action.go
new file mode 100644
index 0000000000..df9f845adc
--- /dev/null
+++ b/modules/structs/action.go
@@ -0,0 +1,25 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package structs
+
+// ActionRunJob represents a job of a run
+// swagger:model
+type ActionRunJob struct {
+	// the action run job id
+	ID int64 `json:"id"`
+	// the repository id
+	RepoID int64 `json:"repo_id"`
+	// the owner id
+	OwnerID int64 `json:"owner_id"`
+	// the action run job name
+	Name string `json:"name"`
+	// the action run job needed ids
+	Needs []string `json:"needs"`
+	// the action run job labels to run on
+	RunsOn []string `json:"runs_on"`
+	// the action run job latest task id
+	TaskID int64 `json:"task_id"`
+	// the action run job status
+	Status string `json:"status"`
+}
diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go
index 329242d9f6..d0cfef8e48 100644
--- a/routers/api/v1/admin/runners.go
+++ b/routers/api/v1/admin/runners.go
@@ -24,3 +24,23 @@ func GetRegistrationToken(ctx *context.APIContext) {
 
 	shared.GetRegistrationToken(ctx, 0, 0)
 }
+
+// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
+func SearchActionRunJobs(ctx *context.APIContext) {
+	// swagger:operation GET /admin/runners/jobs admin adminSearchRunJobs
+	// ---
+	// summary: Search action jobs according filter conditions
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: labels
+	//   in: query
+	//   description: a comma separated list of run job labels to search for
+	//   type: string
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/RunJobList"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	shared.GetActionRunJobs(ctx, 0, 0)
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 4928c9ff58..89338d6977 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -822,6 +822,7 @@ func Routes() *web.Route {
 
 			m.Group("/runners", func() {
 				m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
+				m.Get("/jobs", reqToken(), reqChecker, act.SearchActionRunJobs)
 			})
 		})
 	}
@@ -975,6 +976,7 @@ func Routes() *web.Route {
 
 				m.Group("/runners", func() {
 					m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
+					m.Get("/jobs", reqToken(), user.SearchActionRunJobs)
 				})
 			})
 
@@ -1631,6 +1633,7 @@ func Routes() *web.Route {
 			})
 			m.Group("/runners", func() {
 				m.Get("/registration-token", admin.GetRegistrationToken)
+				m.Get("/jobs", admin.SearchActionRunJobs)
 			})
 			if setting.Quota.Enabled {
 				m.Group("/quota", func() {
diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go
index 03a1fa8ccc..8cd2e00e00 100644
--- a/routers/api/v1/org/action.go
+++ b/routers/api/v1/org/action.go
@@ -189,6 +189,31 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
 	shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
 }
 
+// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
+func (Action) SearchActionRunJobs(ctx *context.APIContext) {
+	// swagger:operation GET /orgs/{org}/actions/runners/jobs organization orgSearchRunJobs
+	// ---
+	// summary: Search for organization's action jobs according filter conditions
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: org
+	//   in: path
+	//   description: name of the organization
+	//   type: string
+	//   required: true
+	// - name: labels
+	//   in: query
+	//   description: a comma separated list of run job labels to search for
+	//   type: string
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/RunJobList"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	shared.GetActionRunJobs(ctx, ctx.Org.Organization.ID, 0)
+}
+
 // ListVariables list org-level variables
 func (Action) ListVariables(ctx *context.APIContext) {
 	// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go
index 0c7506b13b..2ff52c3744 100644
--- a/routers/api/v1/repo/action.go
+++ b/routers/api/v1/repo/action.go
@@ -507,6 +507,36 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
 	shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
 }
 
+// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
+func (Action) SearchActionRunJobs(ctx *context.APIContext) {
+	// swagger:operation GET /repos/{owner}/{repo}/actions/runners/jobs repository repoSearchRunJobs
+	// ---
+	// summary: Search for repository's action jobs according filter conditions
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: owner
+	//   in: path
+	//   description: owner of the repo
+	//   type: string
+	//   required: true
+	// - name: repo
+	//   in: path
+	//   description: name of the repo
+	//   type: string
+	//   required: true
+	// - name: labels
+	//   in: query
+	//   description: a comma separated list of run job labels to search for
+	//   type: string
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/RunJobList"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	shared.GetActionRunJobs(ctx, 0, ctx.Repo.Repository.ID)
+}
+
 var _ actions_service.API = new(Action)
 
 // Action implements actions_service.API
diff --git a/routers/api/v1/shared/runners.go b/routers/api/v1/shared/runners.go
index f184786d7d..53761a07e9 100644
--- a/routers/api/v1/shared/runners.go
+++ b/routers/api/v1/shared/runners.go
@@ -6,8 +6,11 @@ package shared
 import (
 	"errors"
 	"net/http"
+	"strings"
 
 	actions_model "code.gitea.io/gitea/models/actions"
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/structs"
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/services/context"
 )
@@ -30,3 +33,48 @@ func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
 
 	ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
 }
+
+// RunJobList is a list of action run jobs
+// swagger:response RunJobList
+type RunJobList struct {
+	// in:body
+	Body []*structs.ActionRunJob `json:"body"`
+}
+
+func GetActionRunJobs(ctx *context.APIContext, ownerID, repoID int64) {
+	labels := strings.Split(ctx.FormTrim("labels"), ",")
+
+	total, err := db.Find[actions_model.ActionRunJob](ctx, &actions_model.FindTaskOptions{
+		Status:  []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusRunning},
+		OwnerID: ownerID,
+		RepoID:  repoID,
+	})
+	if err != nil {
+		ctx.Error(http.StatusInternalServerError, "CountWaitingActionRunJobs", err)
+		return
+	}
+
+	res := new(RunJobList)
+	res.Body = fromRunJobModelToResponse(total, labels)
+
+	ctx.JSON(http.StatusOK, res)
+}
+
+func fromRunJobModelToResponse(job []*actions_model.ActionRunJob, labels []string) []*structs.ActionRunJob {
+	var res []*structs.ActionRunJob
+	for i := range job {
+		if job[i].ItRunsOn(labels) {
+			res = append(res, &structs.ActionRunJob{
+				ID:      job[i].ID,
+				RepoID:  job[i].RepoID,
+				OwnerID: job[i].OwnerID,
+				Name:    job[i].Name,
+				Needs:   job[i].Needs,
+				RunsOn:  job[i].RunsOn,
+				TaskID:  job[i].TaskID,
+				Status:  job[i].Status.String(),
+			})
+		}
+	}
+	return res
+}
diff --git a/routers/api/v1/user/runners.go b/routers/api/v1/user/runners.go
index dc4c187ffe..5e8cdbeb58 100644
--- a/routers/api/v1/user/runners.go
+++ b/routers/api/v1/user/runners.go
@@ -28,3 +28,25 @@ func GetRegistrationToken(ctx *context.APIContext) {
 
 	shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
 }
+
+// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
+func SearchActionRunJobs(ctx *context.APIContext) {
+	// swagger:operation GET /user/actions/runners/jobs user userSearchRunJobs
+	// ---
+	// summary: Search for user's action jobs according filter conditions
+	// produces:
+	// - application/json
+	// parameters:
+	// - name: labels
+	//   in: query
+	//   description: a comma separated list of run job labels to search for
+	//   type: string
+	// responses:
+	//   "200":
+	//     "$ref": "#/responses/RunJobList"
+	//   "401":
+	//     "$ref": "#/responses/unauthorized"
+	//   "403":
+	//     "$ref": "#/responses/forbidden"
+	shared.GetActionRunJobs(ctx, ctx.Doer.ID, 0)
+}
diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go
index f38933226b..7ed3f88f6c 100644
--- a/routers/web/shared/actions/runners.go
+++ b/routers/web/shared/actions/runners.go
@@ -79,7 +79,7 @@ func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int
 			Page:     page,
 			PageSize: 30,
 		},
-		Status:   actions_model.StatusUnknown, // Unknown means all
+		Status:   []actions_model.Status{actions_model.StatusUnknown}, // Unknown means all
 		RunnerID: runner.ID,
 	}
 
diff --git a/services/actions/clear_tasks.go b/services/actions/clear_tasks.go
index 67373782d5..f146c22372 100644
--- a/services/actions/clear_tasks.go
+++ b/services/actions/clear_tasks.go
@@ -19,7 +19,7 @@ import (
 // StopZombieTasks stops the task which have running status, but haven't been updated for a long time
 func StopZombieTasks(ctx context.Context) error {
 	return stopTasks(ctx, actions_model.FindTaskOptions{
-		Status:        actions_model.StatusRunning,
+		Status:        []actions_model.Status{actions_model.StatusRunning},
 		UpdatedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.ZombieTaskTimeout).Unix()),
 	})
 }
@@ -27,7 +27,7 @@ func StopZombieTasks(ctx context.Context) error {
 // StopEndlessTasks stops the tasks which have running status and continuous updates, but don't end for a long time
 func StopEndlessTasks(ctx context.Context) error {
 	return stopTasks(ctx, actions_model.FindTaskOptions{
-		Status:        actions_model.StatusRunning,
+		Status:        []actions_model.Status{actions_model.StatusRunning},
 		StartedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.EndlessTaskTimeout).Unix()),
 	})
 }
diff --git a/services/actions/interface.go b/services/actions/interface.go
index d4fa782fec..76bee6f153 100644
--- a/services/actions/interface.go
+++ b/services/actions/interface.go
@@ -25,4 +25,6 @@ type API interface {
 	UpdateVariable(*context.APIContext)
 	// GetRegistrationToken get registration token
 	GetRegistrationToken(*context.APIContext)
+	// SearchActionRunJobs get pending Action run jobs
+	SearchActionRunJobs(*context.APIContext)
 }
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index f726176883..1832e9d732 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -992,6 +992,34 @@
         }
       }
     },
+    "/admin/runners/jobs": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "admin"
+        ],
+        "summary": "Search action jobs according filter conditions",
+        "operationId": "adminSearchRunJobs",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "a comma separated list of run job labels to search for",
+            "name": "labels",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/RunJobList"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          }
+        }
+      }
+    },
     "/admin/runners/registration-token": {
       "get": {
         "produces": [
@@ -2284,6 +2312,41 @@
         }
       }
     },
+    "/orgs/{org}/actions/runners/jobs": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "organization"
+        ],
+        "summary": "Search for organization's action jobs according filter conditions",
+        "operationId": "orgSearchRunJobs",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "name of the organization",
+            "name": "org",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "a comma separated list of run job labels to search for",
+            "name": "labels",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/RunJobList"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          }
+        }
+      }
+    },
     "/orgs/{org}/actions/runners/registration-token": {
       "get": {
         "produces": [
@@ -4639,6 +4702,48 @@
         }
       }
     },
+    "/repos/{owner}/{repo}/actions/runners/jobs": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "repository"
+        ],
+        "summary": "Search for repository's action jobs according filter conditions",
+        "operationId": "repoSearchRunJobs",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "owner of the repo",
+            "name": "owner",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "name of the repo",
+            "name": "repo",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "description": "a comma separated list of run job labels to search for",
+            "name": "labels",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/RunJobList"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          }
+        }
+      }
+    },
     "/repos/{owner}/{repo}/actions/runners/registration-token": {
       "get": {
         "produces": [
@@ -17399,6 +17504,37 @@
         }
       }
     },
+    "/user/actions/runners/jobs": {
+      "get": {
+        "produces": [
+          "application/json"
+        ],
+        "tags": [
+          "user"
+        ],
+        "summary": "Search for user's action jobs according filter conditions",
+        "operationId": "userSearchRunJobs",
+        "parameters": [
+          {
+            "type": "string",
+            "description": "a comma separated list of run job labels to search for",
+            "name": "labels",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/responses/RunJobList"
+          },
+          "401": {
+            "$ref": "#/responses/unauthorized"
+          },
+          "403": {
+            "$ref": "#/responses/forbidden"
+          }
+        }
+      }
+    },
     "/user/actions/runners/registration-token": {
       "get": {
         "produces": [
@@ -20387,6 +20523,63 @@
       },
       "x-go-package": "code.gitea.io/gitea/modules/structs"
     },
+    "ActionRunJob": {
+      "description": "ActionRunJob represents a job of a run",
+      "type": "object",
+      "properties": {
+        "id": {
+          "description": "the action run job id",
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "ID"
+        },
+        "name": {
+          "description": "the action run job name",
+          "type": "string",
+          "x-go-name": "Name"
+        },
+        "needs": {
+          "description": "the action run job needed ids",
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "Needs"
+        },
+        "owner_id": {
+          "description": "the owner id",
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "OwnerID"
+        },
+        "repo_id": {
+          "description": "the repository id",
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "RepoID"
+        },
+        "runs_on": {
+          "description": "the action run job labels to run on",
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "x-go-name": "RunsOn"
+        },
+        "status": {
+          "description": "the action run job status",
+          "type": "string",
+          "x-go-name": "Status"
+        },
+        "task_id": {
+          "description": "the action run job latest task id",
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "TaskID"
+        }
+      },
+      "x-go-package": "code.gitea.io/gitea/modules/structs"
+    },
     "ActionTask": {
       "description": "ActionTask represents a ActionTask",
       "type": "object",
@@ -28678,6 +28871,15 @@
         }
       }
     },
+    "RunJobList": {
+      "description": "RunJobList is a list of action run jobs",
+      "schema": {
+        "type": "array",
+        "items": {
+          "$ref": "#/definitions/ActionRunJob"
+        }
+      }
+    },
     "SearchResults": {
       "description": "SearchResults",
       "schema": {
diff --git a/tests/integration/api_admin_actions_test.go b/tests/integration/api_admin_actions_test.go
new file mode 100644
index 0000000000..22590dc4c4
--- /dev/null
+++ b/tests/integration/api_admin_actions_test.go
@@ -0,0 +1,39 @@
+// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	actions_model "code.gitea.io/gitea/models/actions"
+	auth_model "code.gitea.io/gitea/models/auth"
+	"code.gitea.io/gitea/models/unittest"
+	"code.gitea.io/gitea/routers/api/v1/shared"
+	"code.gitea.io/gitea/tests"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAPISearchActionJobs_GlobalRunner(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393})
+	adminUsername := "user1"
+	token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
+
+	req := NewRequest(
+		t,
+		"GET",
+		fmt.Sprintf("/api/v1/admin/runners/jobs?labels=%s", "ubuntu-latest"),
+	).AddTokenAuth(token)
+	res := MakeRequest(t, req, http.StatusOK)
+
+	var jobs shared.RunJobList
+	DecodeJSON(t, res, &jobs)
+
+	assert.Len(t, jobs.Body, 1)
+	assert.EqualValues(t, job.ID, jobs.Body[0].ID)
+}
diff --git a/tests/integration/api_org_actions_test.go b/tests/integration/api_org_actions_test.go
new file mode 100644
index 0000000000..c8ebbdf293
--- /dev/null
+++ b/tests/integration/api_org_actions_test.go
@@ -0,0 +1,38 @@
+// Copyright 2025 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	actions_model "code.gitea.io/gitea/models/actions"
+	auth_model "code.gitea.io/gitea/models/auth"
+	"code.gitea.io/gitea/models/unittest"
+	"code.gitea.io/gitea/routers/api/v1/shared"
+	"code.gitea.io/gitea/tests"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAPISearchActionJobs_OrgRunner(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	session := loginUser(t, "user1")
+	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
+
+	job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 395})
+
+	req := NewRequest(t, "GET",
+		fmt.Sprintf("/api/v1/orgs/org3/actions/runners/jobs?labels=%s", "fedora")).
+		AddTokenAuth(token)
+	res := MakeRequest(t, req, http.StatusOK)
+
+	var jobs shared.RunJobList
+	DecodeJSON(t, res, &jobs)
+
+	assert.Len(t, jobs.Body, 1)
+	assert.EqualValues(t, job.ID, jobs.Body[0].ID)
+}
diff --git a/tests/integration/api_repo_actions_test.go b/tests/integration/api_repo_actions_test.go
new file mode 100644
index 0000000000..9c3b6aa2b6
--- /dev/null
+++ b/tests/integration/api_repo_actions_test.go
@@ -0,0 +1,43 @@
+// Copyright 2025 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+	"net/http"
+	"testing"
+
+	actions_model "code.gitea.io/gitea/models/actions"
+	auth_model "code.gitea.io/gitea/models/auth"
+	repo_model "code.gitea.io/gitea/models/repo"
+	"code.gitea.io/gitea/models/unittest"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/routers/api/v1/shared"
+	"code.gitea.io/gitea/tests"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAPISearchActionJobs_RepoRunner(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+	token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository)
+	job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393})
+
+	req := NewRequestf(
+		t,
+		"GET",
+		"/api/v1/repos/%s/%s/actions/runners/jobs?labels=%s",
+		repo.OwnerName, repo.Name,
+		"ubuntu-latest",
+	).AddTokenAuth(token)
+	res := MakeRequest(t, req, http.StatusOK)
+
+	var jobs shared.RunJobList
+	DecodeJSON(t, res, &jobs)
+
+	assert.Len(t, jobs.Body, 1)
+	assert.EqualValues(t, job.ID, jobs.Body[0].ID)
+}
diff --git a/tests/integration/api_user_actions_test.go b/tests/integration/api_user_actions_test.go
new file mode 100644
index 0000000000..f9c9c1df4e
--- /dev/null
+++ b/tests/integration/api_user_actions_test.go
@@ -0,0 +1,38 @@
+// Copyright 2025 The Forgejo Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+	"fmt"
+	"net/http"
+	"testing"
+
+	actions_model "code.gitea.io/gitea/models/actions"
+	auth_model "code.gitea.io/gitea/models/auth"
+	"code.gitea.io/gitea/models/unittest"
+	"code.gitea.io/gitea/routers/api/v1/shared"
+	"code.gitea.io/gitea/tests"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestAPISearchActionJobs_UserRunner(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+
+	normalUsername := "user2"
+	session := loginUser(t, normalUsername)
+	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
+	job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 394})
+
+	req := NewRequest(t, "GET",
+		fmt.Sprintf("/api/v1/user/actions/runners/jobs?labels=%s", "debian-latest")).
+		AddTokenAuth(token)
+	res := MakeRequest(t, req, http.StatusOK)
+
+	var jobs shared.RunJobList
+	DecodeJSON(t, res, &jobs)
+
+	assert.Len(t, jobs.Body, 1)
+	assert.EqualValues(t, job.ID, jobs.Body[0].ID)
+}