forgeapi lets your Go code read and write pull requests, issues, merges, checks, releases and labels on GitHub, GitLab, Gitea and Forgejo through one set of types.
It replaces four API clients and the differences in routes, field names and value shapes you would otherwise handle. It has no operations for comments, reviews or file contents. It needs Go 1.27.2 or later, is at v1 and is licensed under Apache-2.0. It depends only on atomicfile, httpx, jsoncap, runesafe, ssrf and urlform, six modules by the same author.
forgeapi is built for Go tools that span several forges.
- Each operation is one method with the same result types on all four products. A field a product does not supply keeps its zero value, the unknown member for an enumeration.
- Each operation's godoc lists its route, request cost and differing fields per product, and SUPPORT.md maps operations to products.
- It serves github.com, gitlab.com, and Gitea and Forgejo instances. GitHub Enterprise Server and self-managed GitLab run the same code but were not measured.
- A client paces itself to the rate limit and reports a repository that moved.
credsruns the OAuth device flow on GitHub and GitLab and refreshes tokens.gitcredhands them to git.
Consider go-github if you target GitHub alone and want services that mirror its API documentation. Consider go-scm if you need Bitbucket, Gitee or Gogs, or comment and file endpoints. Consider git-pkgs/forge if you need reviews, branches, Bitbucket Cloud or Gerrit, or a command-line tool.
go get github.com/cplieger/forgeapi@latestclient, err := github.New(
forgeapi.Connection{WebBaseURL: "https://github.com"},
forgeapi.WithCredentialSource(cred),
)
if err != nil {
return err
}
defer client.Close()
repos, err := client.ListRepos(ctx, forgeapi.WithPageBound(1))
if err != nil {
return err
}
prs, err := client.ListPRs(ctx, repos.Items[0].Ref, forgeapi.WithState(forgeapi.ListStateOpen))example_test.go holds the compiling form, with a credential source and error handling.
The github, gitlab and gitea packages each hold one client, and gitea serves Forgejo too. A client asks its CredentialSource for a token on every request, so a rotating token and a static one use the same code. Take each RepoRef from a call such as ListRepos rather than building one, because it carries its family's own spelling of the repository path. Call Close when you are done, to release the client's connection pool.
families.Opendetects which forge a URL points at and returns aforgeapi.Core. Connecting to a forge covers what detection costs.- An instance on a private network or on plain
httpis refused until you passforgeapi.WithPrivateAddresses(true)orforgeapi.WithPlaintextHTTP(true). Plain HTTP sends the token and every request in cleartext. forgeapi.WithMutations(false)makes a client read-only. In tests,forgeapi.WithWireTransporttakes anhttp.RoundTripperthat answers requests without a network.- The
credspackageExamplesigns a user in and builds a client on the stored credential. Let only one process refresh tokens from a store, because two can lose a rotated token and force the user to sign in again. Credentials covers the store and the git credential helper.
Coreis what every client satisfies. It joins eight interfaces,Identity,Repos,PullRequests,Merges,Checks,Issues,CapabilitiesandGovernor, plusClose. Declare the narrowest one your code uses.ReleasesandLabelssit outsideCore, and all three clients implement both. Reach them from aCorewith a type assertion such asrel, ok := client.(forgeapi.Releases).- The writes are
CreatePR,ClosePR,ReopenPR,MergePR,RerunFailedChecks,CreateIssue,CloseIssueandCreateRelease. Every other operation reads. Checks.CommitStatusreturns a commit's combined check verdict.Checks.ListRunslists GitHub Actions runs, GitLab pipelines, and Gitea and Forgejo Actions runs.Connectiondescribes an instance,RepoRefandPRRefaddress a repository and a pull request, andOptionandListOptionconfigure a client and a list call.- Every list answers a
Pagewith aNextcursor, and Pages and continuations covers short pages and resuming. credsholds the device flow, the refreshing source and the file store.gitcredholds the git credential helper.
Per-symbol detail is on pkg.go.dev.
A caller can branch on these three outcomes completely. Every operation of a client, and families.Open, returns a forge or library failure as the *forgeapi.Error pointer, so recover it with errors.As and branch on its code:
var ferr *forgeapi.Error
if errors.As(err, &ferr) && ferr.Code == forgeapi.CodeRepoRefStale { ... }Error carries an ErrorKind and a Code, because one HTTP status means different things per product and per operation. Error.Retryable says whether another attempt may succeed: a network failure, a throttle, and a 408, 429 or 5xx status. Error.Message is the forge's own text, so never branch on it. Error wraps no other error, and the library declares no sentinel errors of its own.
A cancelled or expired context returns Go's own context.Canceled or context.DeadlineExceeded unchanged, so errors.Is works on it. Each operation also runs under its own deadline, Budget.OperationTimeout, 20 seconds by default, which expires as context.DeadlineExceeded too. To tell whether your own context ended, check ctx.Err(). The creds file store and the gitcred helper return ordinary Go errors when a read or write fails.
forgeapi leaves these out on purpose. Non-goals gives the reason for each.
- A generic request method, or routes you add to a client
- A vendor SDK behind any of the clients
- Receiving webhooks
- Git operations such as clone, fetch and push
- A "partly supported" mark in SUPPORT.md. A difference between products is stated on the field it affects
- Reading a proxy from the environment
- A polling loop in the API
- Code scanning
- The OAuth authorization-code grant. To use it, implement
CredentialSourceyourself.
- Connecting to a forge covers connections, forge detection, options and capabilities.
- Pages and continuations explains paging and resuming a list.
- Lists across repositories covers
ListMyPRsandListMyIssues. - Renamed and moved repositories says what a call answers after a move.
- Request prices and the budget covers call costs and pacing.
- Credentials covers sign-in, token refresh, the store and the git credential helper.
- Tested against lists the measured instances and versions.
- Non-goals explains what is left out and why.
See CONTRIBUTING.md.
This project is built with care and follows security best practices, but it is intended for personal / self-hosted use. No guarantees of fitness for production environments. Use at your own risk.
This project was built with AI-assisted tooling using Claude, GPT, and Kiro. The human maintainer defines architecture, supervises implementation, and makes all final decisions.
Apache-2.0. See LICENSE.