Rulesets
Read-only rulesets surface. Mirrors GitHub's
/repos/{owner}/{repo}/rulesets
response shape. shithub synthesizes ruleset rows from the
branch_protection_rules table — one branch-protection row
becomes one ruleset, named after its pattern, with each
configured field projected as a typed rule object.
All endpoints require Authorization: Bearer <pat> with
repo:read and gate on ActionRepoRead. The
common API conventions apply.
Ruleset shape
{
"id": 17,
"name": "Pattern: main",
"target": "branch",
"source_type": "Repository",
"source": "alice/demo",
"enforcement": "active",
"conditions": {
"ref_name": {
"include": ["refs/heads/main"],
"exclude": []
}
},
"rules": [
{
"type": "pull_request",
"parameters": {
"required_approving_review_count": 1,
"dismiss_stale_reviews_on_push": false,
"require_code_owner_review": true
}
},
{"type": "non_fast_forward"},
{"type": "deletion"},
{"type": "required_signatures"},
{
"type": "required_status_checks",
"parameters": {
"required_status_checks": [{"context": "ci/build"}],
"strict_required_status_checks_policy": false
}
}
],
"created_at": "2026-05-12T04:00:00Z",
"updated_at": "2026-05-13T04:00:00Z"
}
Rule types
Each rules[].type maps to one or more columns on the underlying
protection row:
type |
Columns | Notes |
|---|---|---|
pull_request |
require_pr_for_push, required_review_count, require_code_owner_review, dismiss_stale_reviews_on_push |
Emitted when any of these is non-default. parameters.required_approving_review_count is the headline value. |
non_fast_forward |
prevent_force_push |
Force-push gate. No parameters. |
deletion |
prevent_deletion |
Branch-delete gate. No parameters. |
required_signatures |
require_signed_commits |
Verified-signature gate. No parameters. |
required_status_checks |
status_checks_required, dismiss_stale_status_checks_on_push |
parameters.required_status_checks is an array of {context} objects. integration_id is omitted (we don't track per-app integrations). |
Rules an admin hasn't configured are absent from the array — clients should treat missing as "rule not configured" rather than assuming defaults.
Field notes
target— always"branch". Tag rulesets ship in a future sprint when tag protection lands.source_type/source— always"Repository"/"<owner>/<repo>". Org-scoped and enterprise-scoped rulesets are out of scope until shithub grows enterprise-tier accounts.enforcement— always"active". We don't model"disabled"or"evaluate"modes; every row in the table is enforced by the pre-receive hook.conditions.ref_name.include— single-entry array with the pattern prefixed byrefs/heads/. shithub's patterns usefilepath.Matchsemantics (*doesn't cross/), not gh's fnmatch.bypass_actors— not emitted. Bypass / multi-actor exemptions ship in a future sprint.
List rulesets
GET /api/v1/repos/{owner}/{repo}/rulesets
Required scope: repo:read.
Returns every ruleset on the repo, ordered by id. Empty repos and repos with no configured protection rules return [].
Get a single ruleset
GET /api/v1/repos/{owner}/{repo}/rulesets/{id}
Required scope: repo:read.
Returns the single ruleset whose id matches. 404 when the id doesn't belong to this repo — same status as "doesn't exist" so the response doesn't leak existence across repo boundaries.
Rules applying to a branch
GET /api/v1/repos/{owner}/{repo}/rules/branches/{branch}
Required scope: repo:read.
Returns every ruleset whose pattern matches the given branch name. All matching patterns are returned, not just the longest-match — the longest-match heuristic shithub's pre-receive enforcer uses is an internal precedence detail, not a contract surface.
Branch names that contain / (feature/x, release/v1.0) are matched against patterns using filepath.Match: * does not cross path separators, so release/* matches release/v1.0 but not release/v1.0/sub.
Errors
| Status | When |
|---|---|
| 401 | PAT missing/invalid/expired/revoked. |
| 403 | PAT lacks repo:read scope. |
| 404 | Repo not found, ruleset id not found, or cross-repo lookup. |
Mutating rulesets
Creating, updating, and deleting rulesets via REST is not yet shipped. Use the web UI at Settings → Branches → Add rule for now. The shape of the future POST/PATCH/DELETE endpoints will mirror gh's documented surface and will require repo:write + ActionRepoAdmin.
View source
| 1 | # Rulesets |
| 2 | |
| 3 | Read-only rulesets surface. Mirrors GitHub's |
| 4 | [`/repos/{owner}/{repo}/rulesets`](https://docs.github.com/en/rest/repos/rules) |
| 5 | response shape. shithub synthesizes ruleset rows from the |
| 6 | `branch_protection_rules` table — one branch-protection row |
| 7 | becomes one ruleset, named after its pattern, with each |
| 8 | configured field projected as a typed rule object. |
| 9 | |
| 10 | All endpoints require `Authorization: Bearer <pat>` with |
| 11 | `repo:read` and gate on `ActionRepoRead`. The |
| 12 | [common API conventions](overview.md) apply. |
| 13 | |
| 14 | ## Ruleset shape |
| 15 | |
| 16 | ```json |
| 17 | { |
| 18 | "id": 17, |
| 19 | "name": "Pattern: main", |
| 20 | "target": "branch", |
| 21 | "source_type": "Repository", |
| 22 | "source": "alice/demo", |
| 23 | "enforcement": "active", |
| 24 | "conditions": { |
| 25 | "ref_name": { |
| 26 | "include": ["refs/heads/main"], |
| 27 | "exclude": [] |
| 28 | } |
| 29 | }, |
| 30 | "rules": [ |
| 31 | { |
| 32 | "type": "pull_request", |
| 33 | "parameters": { |
| 34 | "required_approving_review_count": 1, |
| 35 | "dismiss_stale_reviews_on_push": false, |
| 36 | "require_code_owner_review": true |
| 37 | } |
| 38 | }, |
| 39 | {"type": "non_fast_forward"}, |
| 40 | {"type": "deletion"}, |
| 41 | {"type": "required_signatures"}, |
| 42 | { |
| 43 | "type": "required_status_checks", |
| 44 | "parameters": { |
| 45 | "required_status_checks": [{"context": "ci/build"}], |
| 46 | "strict_required_status_checks_policy": false |
| 47 | } |
| 48 | } |
| 49 | ], |
| 50 | "created_at": "2026-05-12T04:00:00Z", |
| 51 | "updated_at": "2026-05-13T04:00:00Z" |
| 52 | } |
| 53 | ``` |
| 54 | |
| 55 | ### Rule types |
| 56 | |
| 57 | Each `rules[].type` maps to one or more columns on the underlying |
| 58 | protection row: |
| 59 | |
| 60 | | `type` | Columns | Notes | |
| 61 | |--------|---------|-------| |
| 62 | | `pull_request` | `require_pr_for_push`, `required_review_count`, `require_code_owner_review`, `dismiss_stale_reviews_on_push` | Emitted when any of these is non-default. `parameters.required_approving_review_count` is the headline value. | |
| 63 | | `non_fast_forward` | `prevent_force_push` | Force-push gate. No parameters. | |
| 64 | | `deletion` | `prevent_deletion` | Branch-delete gate. No parameters. | |
| 65 | | `required_signatures` | `require_signed_commits` | Verified-signature gate. No parameters. | |
| 66 | | `required_status_checks` | `status_checks_required`, `dismiss_stale_status_checks_on_push` | `parameters.required_status_checks` is an array of `{context}` objects. `integration_id` is omitted (we don't track per-app integrations). | |
| 67 | |
| 68 | Rules an admin hasn't configured are absent from the array — clients should treat missing as "rule not configured" rather than assuming defaults. |
| 69 | |
| 70 | ### Field notes |
| 71 | |
| 72 | - `target` — always `"branch"`. Tag rulesets ship in a future sprint when tag protection lands. |
| 73 | - `source_type` / `source` — always `"Repository"` / `"<owner>/<repo>"`. Org-scoped and enterprise-scoped rulesets are out of scope until shithub grows enterprise-tier accounts. |
| 74 | - `enforcement` — always `"active"`. We don't model `"disabled"` or `"evaluate"` modes; every row in the table is enforced by the pre-receive hook. |
| 75 | - `conditions.ref_name.include` — single-entry array with the pattern prefixed by `refs/heads/`. shithub's patterns use `filepath.Match` semantics (`*` doesn't cross `/`), not gh's fnmatch. |
| 76 | - `bypass_actors` — not emitted. Bypass / multi-actor exemptions ship in a future sprint. |
| 77 | |
| 78 | ## List rulesets |
| 79 | |
| 80 | ``` |
| 81 | GET /api/v1/repos/{owner}/{repo}/rulesets |
| 82 | ``` |
| 83 | |
| 84 | Required scope: `repo:read`. |
| 85 | |
| 86 | Returns every ruleset on the repo, ordered by id. Empty repos and repos with no configured protection rules return `[]`. |
| 87 | |
| 88 | ## Get a single ruleset |
| 89 | |
| 90 | ``` |
| 91 | GET /api/v1/repos/{owner}/{repo}/rulesets/{id} |
| 92 | ``` |
| 93 | |
| 94 | Required scope: `repo:read`. |
| 95 | |
| 96 | Returns the single ruleset whose id matches. 404 when the id doesn't belong to this repo — same status as "doesn't exist" so the response doesn't leak existence across repo boundaries. |
| 97 | |
| 98 | ## Rules applying to a branch |
| 99 | |
| 100 | ``` |
| 101 | GET /api/v1/repos/{owner}/{repo}/rules/branches/{branch} |
| 102 | ``` |
| 103 | |
| 104 | Required scope: `repo:read`. |
| 105 | |
| 106 | Returns every ruleset whose pattern matches the given branch name. **All** matching patterns are returned, not just the longest-match — the longest-match heuristic shithub's pre-receive enforcer uses is an internal precedence detail, not a contract surface. |
| 107 | |
| 108 | Branch names that contain `/` (`feature/x`, `release/v1.0`) are matched against patterns using `filepath.Match`: `*` does not cross path separators, so `release/*` matches `release/v1.0` but not `release/v1.0/sub`. |
| 109 | |
| 110 | ## Errors |
| 111 | |
| 112 | | Status | When | |
| 113 | |-------:|-------------------------------------------------| |
| 114 | | 401 | PAT missing/invalid/expired/revoked. | |
| 115 | | 403 | PAT lacks `repo:read` scope. | |
| 116 | | 404 | Repo not found, ruleset id not found, or cross-repo lookup. | |
| 117 | |
| 118 | ## Mutating rulesets |
| 119 | |
| 120 | Creating, updating, and deleting rulesets via REST is not yet shipped. Use the web UI at `Settings → Branches → Add rule` for now. The shape of the future POST/PATCH/DELETE endpoints will mirror gh's documented surface and will require `repo:write` + `ActionRepoAdmin`. |