API overview
shithub exposes a small REST API at /api/v1/. The user-facing API is
PAT-authenticated, JSON-bodied, and CSRF-exempt. The Actions runner API
under the same prefix uses runner registration tokens plus per-job JWTs
instead of PATs.
Status. The API is intentionally narrow today. Endpoints currently shipped:
GET /api/v1/user, the/api/v1/repos/{owner}/{repo}/check-runsfamily, and the/api/v1/user/starred*stars endpoints, plus the Actions lifecycle routes in Actions workflow API. Other sections of this reference (Issues, Pull requests, Webhooks, etc.) describe the planned shape and will land in subsequent sprints. Pages that document planned-only endpoints carry a banner.
Authentication
Every API request requires a personal access token. See Personal access tokens for how to create one. Runner endpoints documented in Actions runner API are the exception; they reject PATs and require runner credentials.
Authorization: Bearer shp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authorization: token shp_… is also accepted as a synonym.
A request with no Authorization header — or with an invalid /
expired / revoked PAT — returns 401 Unauthorized with the
canonical JSON error envelope. The response also carries a
WWW-Authenticate: Bearer realm="shithub", error="invalid_token"
challenge so HTTP-aware clients can reauthenticate cleanly.
{"error": "invalid token"}
A request whose PAT lacks the scope a route requires returns
403 Forbidden with the required scope surfaced in the
X-Accepted-OAuth-Scopes response header. The token's actual
scopes are echoed on every PAT-authenticated response (including
errors) via X-OAuth-Scopes, so clients can build precise
"missing scope X, present scopes Y, Z" error messages without
parsing the body:
HTTP/1.1 403 Forbidden
X-OAuth-Scopes: user:read
X-Accepted-OAuth-Scopes: repo:write
Content-Type: application/json; charset=utf-8
{"error":"token lacks required scope: repo:write"}
Scopes
Every route declares the scope(s) a token must hold. See scopes table.
Scopes are grants only — a token cannot do something the
underlying user cannot. Holding repo does not let a token push
to a repo the user has no access to.
Conventions
- Base URL:
https://<your-instance>/api/v1/ - Content type:
application/json; charset=utf-8for request bodies and responses. - Error responses:
{"error": "<short message>"}with a conventional HTTP status. - Cache-Control: every API response sets
no-store. - Pagination: list endpoints accept
?per_page=(default 30, max 100) and return an RFC 8288Link:header withnext,prev,first,lastURLs. URLs are absolute and use the instance's configured public base URL. Forward-only feeds emit onlynext/prev(nofirst/last) when totals are expensive to compute. Cursor pagination on hot lists uses?cursor=…and returns the next cursor in the sameLink:header. - Body cap: request bodies are capped at 256 KiB. Larger
payloads return
413. - Rate limits: every response includes
X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Resetheaders (Unix timestamp). Exceeding the limit returns429with the canonical error envelope, aRetry-Afterheader (seconds), andX-RateLimit-Remaining: 0. Defaults: 5000 / hour for PAT callers (keyed by token id) and 60 / hour for anonymous callers (keyed by remote IP). Operators tune viaSHITHUB_RATELIMIT__API__AUTHED_PER_HOURandSHITHUB_RATELIMIT__API__ANON_PER_HOUR. - Request id: every response echoes
X-Request-Id. If the caller sends one (matching[a-z0-9/:_-]{1,128}) it's reused; otherwise the server generates a fresh 16-byte hex id. Quote this id in support reports — it correlates against the server logs.
Capability discovery
GET /api/v1/meta is unauthenticated and returns the server's
version stamp plus a list of feature capability strings. Clients
use it to gate optional features without trial-and-error:
{
"version": "v0.2.0",
"commit": "abc1234",
"built_at": "2026-05-12T03:00:00Z",
"capabilities": ["pat-auth", "check-runs", "stars", "actions-lifecycle"]
}
New capability identifiers are appended as feature batches land; existing entries are stable.
Versioning
The path prefix /api/v1/ is the version. Backwards-incompatible
changes will land under /api/v2/. Additions (new endpoints, new
fields on responses) are not breaking and land under v1.
Date format
All timestamps are RFC 3339 UTC: 2026-05-09T16:30:00Z.
ID stability
Numeric IDs are stable for the life of the row; reusing a name
slot doesn't reuse the ID. URLs that take a {owner} and {repo}
will redirect after a rename — but external callers should
prefer numeric IDs where the API exposes them.
View source
| 1 | # API overview |
| 2 | |
| 3 | shithub exposes a small REST API at `/api/v1/`. The user-facing API is |
| 4 | PAT-authenticated, JSON-bodied, and CSRF-exempt. The Actions runner API |
| 5 | under the same prefix uses runner registration tokens plus per-job JWTs |
| 6 | instead of PATs. |
| 7 | |
| 8 | > **Status.** The API is intentionally narrow today. Endpoints |
| 9 | > currently shipped: `GET /api/v1/user`, the |
| 10 | > `/api/v1/repos/{owner}/{repo}/check-runs` family, and the |
| 11 | > `/api/v1/user/starred*` stars endpoints, plus the Actions lifecycle |
| 12 | > routes in [Actions workflow API](actions.md). Other sections of this |
| 13 | > reference (Issues, Pull requests, Webhooks, etc.) describe the |
| 14 | > **planned** shape and will land in subsequent sprints. Pages |
| 15 | > that document planned-only endpoints carry a banner. |
| 16 | |
| 17 | ## Authentication |
| 18 | |
| 19 | Every API request requires a personal access token. See |
| 20 | [Personal access tokens](../user/personal-access-tokens.md) for |
| 21 | how to create one. Runner endpoints documented in |
| 22 | [Actions runner API](actions-runner.md) are the exception; they reject |
| 23 | PATs and require runner credentials. |
| 24 | |
| 25 | ``` |
| 26 | Authorization: Bearer shp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
| 27 | ``` |
| 28 | |
| 29 | `Authorization: token shp_…` is also accepted as a synonym. |
| 30 | |
| 31 | A request with no `Authorization` header — or with an invalid / |
| 32 | expired / revoked PAT — returns `401 Unauthorized` with the |
| 33 | canonical JSON error envelope. The response also carries a |
| 34 | `WWW-Authenticate: Bearer realm="shithub", error="invalid_token"` |
| 35 | challenge so HTTP-aware clients can reauthenticate cleanly. |
| 36 | |
| 37 | ```json |
| 38 | {"error": "invalid token"} |
| 39 | ``` |
| 40 | |
| 41 | A request whose PAT lacks the scope a route requires returns |
| 42 | `403 Forbidden` with the required scope surfaced in the |
| 43 | `X-Accepted-OAuth-Scopes` response header. The token's actual |
| 44 | scopes are echoed on every PAT-authenticated response (including |
| 45 | errors) via `X-OAuth-Scopes`, so clients can build precise |
| 46 | "missing scope X, present scopes Y, Z" error messages without |
| 47 | parsing the body: |
| 48 | |
| 49 | ``` |
| 50 | HTTP/1.1 403 Forbidden |
| 51 | X-OAuth-Scopes: user:read |
| 52 | X-Accepted-OAuth-Scopes: repo:write |
| 53 | Content-Type: application/json; charset=utf-8 |
| 54 | |
| 55 | {"error":"token lacks required scope: repo:write"} |
| 56 | ``` |
| 57 | |
| 58 | ## Scopes |
| 59 | |
| 60 | Every route declares the scope(s) a token must hold. See |
| 61 | [scopes table](../user/personal-access-tokens.md#scopes). |
| 62 | |
| 63 | Scopes are **grants only** — a token cannot do something the |
| 64 | underlying user cannot. Holding `repo` does not let a token push |
| 65 | to a repo the user has no access to. |
| 66 | |
| 67 | ## Conventions |
| 68 | |
| 69 | - **Base URL:** `https://<your-instance>/api/v1/` |
| 70 | - **Content type:** `application/json; charset=utf-8` for |
| 71 | request bodies and responses. |
| 72 | - **Error responses:** `{"error": "<short message>"}` with a |
| 73 | conventional HTTP status. |
| 74 | - **Cache-Control:** every API response sets `no-store`. |
| 75 | - **Pagination:** list endpoints accept `?per_page=` (default 30, |
| 76 | max 100) and return an RFC 8288 `Link:` header with `next`, |
| 77 | `prev`, `first`, `last` URLs. URLs are absolute and use the |
| 78 | instance's configured public base URL. Forward-only feeds emit |
| 79 | only `next` / `prev` (no `first` / `last`) when totals are |
| 80 | expensive to compute. Cursor pagination on hot lists uses |
| 81 | `?cursor=…` and returns the next cursor in the same `Link:` |
| 82 | header. |
| 83 | - **Body cap:** request bodies are capped at 256 KiB. Larger |
| 84 | payloads return `413`. |
| 85 | - **Rate limits:** every response includes `X-RateLimit-Limit`, |
| 86 | `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers (Unix |
| 87 | timestamp). Exceeding the limit returns `429` with the canonical |
| 88 | error envelope, a `Retry-After` header (seconds), and |
| 89 | `X-RateLimit-Remaining: 0`. Defaults: 5000 / hour for PAT |
| 90 | callers (keyed by token id) and 60 / hour for anonymous callers |
| 91 | (keyed by remote IP). Operators tune via |
| 92 | `SHITHUB_RATELIMIT__API__AUTHED_PER_HOUR` and |
| 93 | `SHITHUB_RATELIMIT__API__ANON_PER_HOUR`. |
| 94 | - **Request id:** every response echoes `X-Request-Id`. If the |
| 95 | caller sends one (matching `[a-z0-9/:_-]{1,128}`) it's reused; |
| 96 | otherwise the server generates a fresh 16-byte hex id. Quote |
| 97 | this id in support reports — it correlates against the server |
| 98 | logs. |
| 99 | |
| 100 | ## Capability discovery |
| 101 | |
| 102 | `GET /api/v1/meta` is unauthenticated and returns the server's |
| 103 | version stamp plus a list of feature capability strings. Clients |
| 104 | use it to gate optional features without trial-and-error: |
| 105 | |
| 106 | ```json |
| 107 | { |
| 108 | "version": "v0.2.0", |
| 109 | "commit": "abc1234", |
| 110 | "built_at": "2026-05-12T03:00:00Z", |
| 111 | "capabilities": ["pat-auth", "check-runs", "stars", "actions-lifecycle"] |
| 112 | } |
| 113 | ``` |
| 114 | |
| 115 | New capability identifiers are appended as feature batches land; |
| 116 | existing entries are stable. |
| 117 | |
| 118 | ## Versioning |
| 119 | |
| 120 | The path prefix `/api/v1/` is the version. Backwards-incompatible |
| 121 | changes will land under `/api/v2/`. Additions (new endpoints, new |
| 122 | fields on responses) are not breaking and land under v1. |
| 123 | |
| 124 | ## Date format |
| 125 | |
| 126 | All timestamps are RFC 3339 UTC: `2026-05-09T16:30:00Z`. |
| 127 | |
| 128 | ## ID stability |
| 129 | |
| 130 | Numeric IDs are stable for the life of the row; reusing a name |
| 131 | slot doesn't reuse the ID. URLs that take a `{owner}` and `{repo}` |
| 132 | will redirect after a rename — but external callers should |
| 133 | prefer numeric IDs where the API exposes them. |