Ten forges, one API

One API for every code forge

Work with issues, pull requests, notifications, checks and webhooks on GitHub, GitLab, Forgejo and seven more forges through one typed data model.

Get started
What you get

What the providers handle

You write code against the shared model. Each provider handles the endpoints, pagination, authentication and rate limits of its forge.
One data model
Issues, pull requests, comments, checks and events have the same shape on every forge. The original payload is in the raw field.
Declared capabilities
Each provider declares what it supports, and CI checks the claims against the tests. Call provider.can() to check support before you call an operation.
Verified webhooks
The provider verifies each delivery and turns it into typed events, with the same kinds and actions on every forge.
Small bundles
Import one forge from its subpath. Lite factories leave out webhook code. The GitHub Lite provider is about 24 kB gzipped.
Offline tests
Run your tests against an in-memory forge or replayed responses, with no network.
Typed errors and retries
Rate limits, revoked tokens and missing scopes have their own error classes. After a short rate limit, the provider retries once.
Write it once

The same code on every forge

Register as many providers as you need. Each call goes to the provider that matches its ref.
Notifications from three forges
import { createForges, forgejo, github, gitlab } from 'forges'

const forges = createForges([
  github({ auth: { type: 'token', token: process.env.GITHUB_TOKEN! } }),
  gitlab({ auth: { type: 'token', token: process.env.GITLAB_TOKEN! } }),
  forgejo({ baseUrl: 'https://codeberg.org', auth: { type: 'token', token: process.env.CODEBERG_TOKEN! } }),
])

for await (const notification of forges.notifications.list()) {
  console.log(notification.ref.forge, notification.title)
}
A verified webhook endpoint
import { github } from 'forges/github'

const forge = github({ webhookSecret: process.env.GITHUB_WEBHOOK_SECRET }).create()

export default async function handler(request: Request) {
  const events = await forge.webhooks.ingest({ headers: request.headers, body: await request.text() })
  for (const event of events) {
    console.log(event.kind, event.action, event.summary)
  }
  return new Response(null, { status: 204 })
}
Know before you call

See what each forge can do

Bitbucket has no releases, and Tangled has no checks. Every provider declares what it supports, so your code can check before it calls.
GitHub
GitLab
Bitbucket
Forgejo
Gitea
Gitee
Azure DevOps
Cursor Origin
pushin.eu
Tangled
writes.merge
notifications.list
releases.list
checks.list
search.threads
webhooks.create
ci.runs
  • Native and verified
  • Experimental
  • Not available
Use it for

What you can build

Each card links to a tested example project or a guide.
A notification inbox
Merge the notifications from every forge you configure into one list.
A label bot
Label new threads and close them from a comment command, on any forge.
A release digest
Show the latest release and failing pull requests across repositories.
A webhook receiver
Turn deliveries from any forge into one event type.

Make your first call

Install the package, read an issue and add a second forge.

Copyright © 2026