Platforms

GitHub

REST v3 for everything except review threads, which need GraphQL for their resolved flag. GitHub Enterprise works through baseURL.
IDgithub01 / 05api.github.com
Platform / github

GitHub

Spoken by GitHubProvider. Same resources, same shapes, its own quirks kept inside.

Access

CreatecreateProvider("github")

Importimport { GitHubProvider } from"@agntn/forges/github"

EnvGH_TOKEN, GITHUB_TOKEN

CLIgh auth token

Config~/.config/gh/hosts.yml

Tryexplorer against the docs worker

Header
Authorization: token
Anonymous
60 requests an hour per address
Threads
GraphQL, resolved and outdated flags
Code search
global, owner and repository scope
Templates
repository and owner .github defaults

Address it

ts
import { createProvider } from "@agntn/forges";

const github = await createProvider("github"); // GH_TOKEN, GITHUB_TOKEN, then gh auth token
const enterprise = await createProvider("github", { baseURL: "https://github.example.com/api/v3" });

baseURL is taken as given, so spell out /api/v3 for Enterprise. The token chain asks gh for the host in baseURL, not for github.com.

What it reads

ResourceEndpoint
reposGET /repos/:owner/:repo, GET /orgs/:owner/repos, GET /users/:owner/repos, GET /user/repos for the viewer, GET …/tags
issuesGET /repos/:owner/:repo/issues, GET /search/issues, POST /repos/:owner/:repo/issues, PATCH …/issues/:number, …/issues/:number/comments
pull requestsGET /repos/:owner/:repo/pulls, …/pulls/:number/files, …/commits/:sha/check-runs, …/commits/:sha/status
issue and pull request editsPATCH …/pulls/:number, POST/DELETE …/issues/:number/assignees, POST …/issues/:number/labels, DELETE …/labels/:name
pull request mergesPUT …/pulls/:number/merge, then a read of the pull request
commitsGET /repos/:owner/:repo/commits, …/commits/:sha, …/compare/:base...:head
commit checksGET /repos/:owner/:repo/commits/:ref for the SHA, then …/commits/:sha/status, …/commits/:sha/check-runs
CI runsGET /repos/:owner/:repo/actions/runs, …/actions/runs/:id/jobs, …/actions/jobs/:id, …/actions/jobs/:id/logs
releasesGET /repos/:owner/:repo/releases, …/releases/tags/:tag, POST …/releases, PATCH …/releases/:id
reactionsGET …/issues/:number/reactions, …/issues/comments/:id/reactions next to a read of the issue and the comment
threadsGraphQL reviewThreads, REST POST …/pulls/:number/comments/:id/replies
templatesGET /repos/:owner/:repo/contents/.github/…, then :owner/.github
codeGET /search/code

Pagination reads the Link header, and nextCursor carries its rel="next" URL.

What comes back

Repository.parent from parent, viewerPermission from permissions when the token has any. merge from the allow_*_merge flags, squash_merge_commit_title, squash_merge_commit_message and delete_branch_on_merge, which GitHub returns only with push access and never on a list, so anything less is null. With pull requests off, methods is empty, push access or not. Issue from /issues with pull requests filtered out by the missing pull_request key. PullRequest.mergeable is null while GitHub is still computing it, which it does lazily, so the first read after a push often says null. Commit.filesComplete is true when GitHub confirms a complete file list, collected up to its cap of 3 000 files.

Templates: repository files first, then the owner's .github repository with scope: "owner" and inherited: true. Issue and pull request overrides resolve on their own. A host without the x-github-enterprise-version header that is not github.com is treated as repository scope only.

Gotchas

  • Review threads need a token. GraphQL has no anonymous access and REST has no resolved flag, so there is no anonymous thread list to be had.
  • Anonymous reads share sixty requests an hour per address. A 403 with a rate limit header is a RateLimitError with retryAfter, not a PermissionError.
  • /issues returns pull requests too. Filtered out here. Search returns both, and pullRequests.search keeps the ones with pull_request.
  • Code search: incomplete goes true on a search timeout, when scope enforcement drops a stray row, or past the cap of 1 000 results.
  • Issue search keeps GitHub's qualifier syntax. label:bug is:open works here and is plain text on GitLab and Gitea.
  • releases/tags/:tag serves published releases only. A draft is found by walking the newest releases, five pages of a hundred, and a draft older than that is a NotFoundError by tag.

Where it lives

src/providers/github.ts. Also the template for a new provider. Copy it, do not start from scratch.