Actions workflow runs
Read-only REST surface over the workflow_runs / workflow_jobs
tables. Mirrors GitHub's /repos/{o}/{r}/actions/runs endpoints
so the CLI's gh run list / gh run view / gh run watch
flows have a real REST contract.
Scope: repo:read. Policy gate: ActionRepoRead.
Lifecycle controls (cancel, rerun, approve) live on the separate Actions lifecycle routes — this surface is read-only.
Endpoints
GET /api/v1/repos/{owner}/{repo}/actions/runs[?…]
GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}
GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/jobs
List runs
Query parameters (all optional, all combinable):
workflow_file— exact path match (e.g..shithub/workflows/ci.yml).head_ref— branch / tag name the run was triggered against.actor— username of the user who triggered the run.event—push/pull_request/workflow_dispatch/ …status—queued/running/completed/ …conclusion—success/failure/cancelled/ …page/per_page— standard pagination (≤100; default 30).
Returns recency-sorted runs with the canonical envelope:
[
{
"id": 1042,
"run_number": 27,
"workflow_file": ".shithub/workflows/ci.yml",
"workflow_name": "CI",
"head_sha": "5f3a…",
"head_ref": "trunk",
"event": "push",
"status": "completed",
"conclusion": "success",
"actor_id": 42,
"actor_username": "alice",
"started_at": "2026-05-12T15:00:00Z",
"completed_at": "2026-05-12T15:03:21Z",
"created_at": "2026-05-12T14:59:55Z",
"updated_at": "2026-05-12T15:03:22Z"
}
]
Link: headers carry the standard first/prev/next/last
pagination cursors.
Get a single run
GET /api/v1/repos/{o}/{r}/actions/runs/{run_id}
Cross-repo probes return 404 — a run id is unique globally, but the response status doesn't let you enumerate.
List a run's jobs
GET /api/v1/repos/{o}/{r}/actions/runs/{run_id}/jobs
[
{
"id": 7150,
"run_id": 1042,
"job_index": 0,
"job_key": "build",
"job_name": "Build & test",
"runs_on": "ubuntu-latest",
"status": "completed",
"conclusion": "success",
"cancel_requested": false,
"needs_jobs": [],
"started_at": "2026-05-12T15:00:30Z",
"completed_at": "2026-05-12T15:03:15Z",
"created_at": "2026-05-12T14:59:55Z"
}
]
Jobs come back in job_index order so the listing matches the
workflow's declared graph. needs_jobs is the literal
dependency array from the workflow file — clients can render the
DAG without re-parsing YAML.
Errors
404— repo not visible, or run id doesn't belong to this repo.403— PAT lacksrepo:read.
View source
| 1 | # Actions workflow runs |
| 2 | |
| 3 | Read-only REST surface over the `workflow_runs` / `workflow_jobs` |
| 4 | tables. Mirrors GitHub's `/repos/{o}/{r}/actions/runs` endpoints |
| 5 | so the CLI's `gh run list` / `gh run view` / `gh run watch` |
| 6 | flows have a real REST contract. |
| 7 | |
| 8 | Scope: `repo:read`. Policy gate: `ActionRepoRead`. |
| 9 | |
| 10 | Lifecycle controls (cancel, rerun, approve) live on the |
| 11 | separate Actions lifecycle routes — this surface is **read-only**. |
| 12 | |
| 13 | ## Endpoints |
| 14 | |
| 15 | ``` |
| 16 | GET /api/v1/repos/{owner}/{repo}/actions/runs[?…] |
| 17 | GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id} |
| 18 | GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/jobs |
| 19 | ``` |
| 20 | |
| 21 | ## List runs |
| 22 | |
| 23 | Query parameters (all optional, all combinable): |
| 24 | |
| 25 | - `workflow_file` — exact path match (e.g. `.shithub/workflows/ci.yml`). |
| 26 | - `head_ref` — branch / tag name the run was triggered against. |
| 27 | - `actor` — username of the user who triggered the run. |
| 28 | - `event` — `push` / `pull_request` / `workflow_dispatch` / … |
| 29 | - `status` — `queued` / `running` / `completed` / … |
| 30 | - `conclusion` — `success` / `failure` / `cancelled` / … |
| 31 | - `page` / `per_page` — standard pagination (≤100; default 30). |
| 32 | |
| 33 | Returns recency-sorted runs with the canonical envelope: |
| 34 | |
| 35 | ```json |
| 36 | [ |
| 37 | { |
| 38 | "id": 1042, |
| 39 | "run_number": 27, |
| 40 | "workflow_file": ".shithub/workflows/ci.yml", |
| 41 | "workflow_name": "CI", |
| 42 | "head_sha": "5f3a…", |
| 43 | "head_ref": "trunk", |
| 44 | "event": "push", |
| 45 | "status": "completed", |
| 46 | "conclusion": "success", |
| 47 | "actor_id": 42, |
| 48 | "actor_username": "alice", |
| 49 | "started_at": "2026-05-12T15:00:00Z", |
| 50 | "completed_at": "2026-05-12T15:03:21Z", |
| 51 | "created_at": "2026-05-12T14:59:55Z", |
| 52 | "updated_at": "2026-05-12T15:03:22Z" |
| 53 | } |
| 54 | ] |
| 55 | ``` |
| 56 | |
| 57 | `Link:` headers carry the standard `first`/`prev`/`next`/`last` |
| 58 | pagination cursors. |
| 59 | |
| 60 | ## Get a single run |
| 61 | |
| 62 | ``` |
| 63 | GET /api/v1/repos/{o}/{r}/actions/runs/{run_id} |
| 64 | ``` |
| 65 | |
| 66 | Cross-repo probes return 404 — a run id is unique globally, but |
| 67 | the response status doesn't let you enumerate. |
| 68 | |
| 69 | ## List a run's jobs |
| 70 | |
| 71 | ``` |
| 72 | GET /api/v1/repos/{o}/{r}/actions/runs/{run_id}/jobs |
| 73 | ``` |
| 74 | |
| 75 | ```json |
| 76 | [ |
| 77 | { |
| 78 | "id": 7150, |
| 79 | "run_id": 1042, |
| 80 | "job_index": 0, |
| 81 | "job_key": "build", |
| 82 | "job_name": "Build & test", |
| 83 | "runs_on": "ubuntu-latest", |
| 84 | "status": "completed", |
| 85 | "conclusion": "success", |
| 86 | "cancel_requested": false, |
| 87 | "needs_jobs": [], |
| 88 | "started_at": "2026-05-12T15:00:30Z", |
| 89 | "completed_at": "2026-05-12T15:03:15Z", |
| 90 | "created_at": "2026-05-12T14:59:55Z" |
| 91 | } |
| 92 | ] |
| 93 | ``` |
| 94 | |
| 95 | Jobs come back in `job_index` order so the listing matches the |
| 96 | workflow's declared graph. `needs_jobs` is the literal |
| 97 | dependency array from the workflow file — clients can render the |
| 98 | DAG without re-parsing YAML. |
| 99 | |
| 100 | ## Errors |
| 101 | |
| 102 | - `404` — repo not visible, or run id doesn't belong to this repo. |
| 103 | - `403` — PAT lacks `repo:read`. |