GitHub
Connect GitHub accounts, repositories, and App sources.
The GitHub API connects an Synq instance to GitHub — via the Synq GitHub App, an OAuth account,
or the local gh CLI — and then lets you browse the accounts, repositories, branches and files you can
deploy from. It also manages the push webhooks that drive auto-deploy.
In the dashboard this is the GitHub connection card in Settings and the repo picker in the deploy wizard.
Both cloud and self-hosted
This module is mounted on every instance. The local identity routes and every /api/github/sources route
are self-hosted only and return 404 on a hosted cloud instance (switched off in Synq). Source management additionally requires the active
workspace's owner role.
Connection state
GET /api/github/home is the dashboard's single entry point: it returns { state, accounts, repos } in one
round trip, filtered to the accounts and repos your member role is allowed to see.
GET /api/github/status is narrower — the Settings card's data source — returning { state, accounts, installUrl, cloudUnreachable, customSourcesConfigured }. The last field tells clients that App lifecycle
controls belong to the workspace source manager rather than the legacy Synq App connection.
const result = await ship.github.getHome();
console.log(result);curl "$SYNQ_URL/api/github/home" \
-H "Authorization: Bearer $SYNQ_TOKEN"Connect
Starts or advances the connection flow. The response is intentionally mode-agnostic — the frontend just
reacts to the flow field:
{ connected: true }— already connected, nothing to do.{ connected: false, flow: "redirect", url }— openurl(OAuth handoff or GitHub App install).{ connected: false, flow: "device_code", userCode, verificationUri, ... }— self-hosted device login.{ connected: false, flow: "terminal", command, message }— runcommand(e.g.gh auth login) yourself.
The optional body field source ("oauth" | "cli") forces a specific path when the dashboard shows both
the "Connect Synq App" and "Use gh CLI" buttons; omit it to let the server pick based on the instance's
auth mode.
const result = await ship.github.connect({
source: "oauth",
});
console.log(result);curl -X POST "$SYNQ_URL/api/github/connect" \
-H "Authorization: Bearer $SYNQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{"source":"oauth"}'Disconnect
POST /api/github/disconnectBody (or ?source=): source is "oauth", "cli", or "all" (default "all"). This drops Synq's
stored credentials for that source; it does not uninstall the GitHub App — that happens only when you
remove it from github.com (Synq reacts to the uninstall webhook).
This endpoint does not delete a workspace-owned source. Use DELETE /api/github/sources/:id for that
explicit owner-only operation.
Workspace-owned GitHub App sources
These endpoints are available only on self-hosted instances. All reads are sanitized: private keys, client secrets, webhook secrets, and their encrypted envelope are never returned.
List sources
Returns { data, configuration }. configuration contains publicReady, publicUrl, setupUrl, and
webhookUrl. Each source in data contains public App metadata, health, default status, and its claimed
installations.
One-click manifest
Start the GitHub.com flow with:
The response contains { url, manifest }. Submit manifest as a JSON string in a form field named
manifest to url; the dashboard does this automatically. GitHub redirects to the configured setup page
with code and state, which must be sent to:
POST /api/github/sources/manifest/convert
{ "code": "…", "state": "…" }Conversion consumes the user/workspace-bound state exactly once, encrypts GitHub's generated credentials,
and returns { data, installUrl }.
Manual registration and GitHub Enterprise Server
POST /api/github/sources/manualProp
Type
The API signs an App JWT and verifies /app before saving the source. It returns { data, installUrl }.
PATCH /api/github/sources/:id accepts the same identity and endpoint fields, all optional. Omitted secret
fields retain their existing encrypted values. App ID or API URL cannot change while the source has claimed
installations. POST .../verify refreshes public App metadata and marks bad credentials as needing attention.
Making a source default atomically rebinds existing projects for every GitHub owner installed on that source. Deleting a source promotes another active source when possible and rebinds affected projects; projects with no replacement lose their installation binding and auto-deploy is disabled. Deleting locally does not uninstall the GitHub App itself.
Create a repository
const result = await ship.github.createRepo({
name: "my-app",
private: true,
owner: "acme",
});
console.log(result);curl -X POST "$SYNQ_URL/api/github/repos" \
-H "Authorization: Bearer $SYNQ_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"my-app","private":true,"owner":"acme"}'Prop
Type
Returns 201 with { data } (the created repository).
Browse repos, branches and files
List repos for the connected account (?owner= narrows to one account), or for a named org via
GET /api/github/orgs/:org/repos. Both return { data: [...] } filtered to repos your role may see.
File browsing reads directly from GitHub:
GET /api/github/repos/:owner/:repo/files— directory listing. Query:path(default repo root) andbranch.GET /api/github/repos/:owner/:repo/file— one file's contents. Query:file(defaultpackage.json) andbranch..jsonfiles are parsed and returned as JSON.
const result = await ship.github.getFile({
owner: "acme",
repo: "my-app",
file: "package.json",
branch: "main",
});
console.log(result);curl "$SYNQ_URL/api/github/repos/acme/my-app/file?file=package.json&branch=main" \
-H "Authorization: Bearer $SYNQ_TOKEN"Webhooks
POST /api/github/repos/:owner/:repo/webhooks registers the Synq push webhook on the repo (or reuses an
existing one) so pushes trigger auto-deploy. It takes no request body — the target repo comes from the
path. GET lists the repo's hooks. To remove one, DELETE with the hook id:
DELETE /api/github/repos/:owner/:repo/webhooksProp
Type
Clone token
GET /api/github/repos/:owner/:repo/clone-token mints a short-lived (under an hour) GitHub App installation
token and returns { token, cloneUrl, command } — a ready-to-run git clone. This is the same credential
the build pipeline clones with, and is available only when the Synq GitHub App is installed for the owner.
Errors
400 — Not connected to GitHub
Returned by the repo-listing routes when no usable GitHub source (App installation, OAuth account, or gh
CLI) is configured. Connect first with POST /api/github/connect, then retry.
409 — no installation token
clone-token needs a GitHub App installation for the owner. In gh CLI or PAT modes there is no
installation token, so this route 409s. Install the Synq GitHub App on that account to use clone tokens.
503 — cloud_unreachable
Hosted-cloud only, switched off in Synq. On an instance connected to a hosted cloud, GitHub OAuth and
App-install URLs are resolved through that cloud; if it is unreachable, connect returns 503 rather than a
dead install link. A Synq install never connects to a hosted cloud, so it doesn't see this error.
An instance configured with its own self-hosted GitHub App does not
use this cloud proxy; GITHUB_AUTH_MODE=auto selects the complete local App configuration first.
404 on a repo route
The :owner/:repo isn't visible to the active GitHub source (private, not granted to your member role, or
does not exist). Confirm the repo appears in GET /api/github/home first.
Operations
Connect
| Operation | SDK | REST API |
|---|---|---|
| Read GitHub connection status. | github.getStatus(input?) | GET /api/github/statusgithub:read |
| Read the GitHub overview for the current identity. | github.getHome() | GET /api/github/homegithub:read |
| Start a supported GitHub connection flow. | github.connect(input?) | POST /api/github/connectgithub:write |
| Claim an authorized GitHub App installation. | github.claimInstallation(input) | POST /api/github/installations/claimgithub:write · Self-hosted |
| Read local Git identity availability. | github.getLocalStatus() | GET /api/github/local-statusgithub:read · Self-hosted |
| Poll an in-progress connection flow. | github.pollConnect() | GET /api/github/connect/pollgithub:read · Self-hosted |
| Configure the installation’s GitHub token. | github.setInstanceToken(input) | POST /api/github/instance-tokengithub:admin · Self-hosted |
| Disconnect a GitHub identity or source association. | github.disconnect(input?) | POST /api/github/disconnectgithub:admin |
Repositories and source
| Operation | SDK | REST API |
|---|---|---|
| List accessible repositories. | github.listRepos(input?) | GET /api/github/reposgithub:list |
| List repositories in a GitHub organization. | github.listOrgRepos(input) | GET /api/github/orgs/:org/reposgithub:list |
| Read repository metadata. | github.getRepo(input) | GET /api/github/repos/:owner/:repogithub:read |
| Create a repository through the configured GitHub identity. | github.createRepo(input) | POST /api/github/reposgithub:write |
| Delete an authorized repository. | github.deleteRepo(input) | DELETE /api/github/repos/:owner/:repogithub:admin |
| List repository branches. | github.listBranches(input) | GET /api/github/repos/:owner/:repo/branchesgithub:list |
| Obtain an authorized repository clone credential. | github.getCloneToken(input) | GET /api/github/repos/:owner/:repo/clone-tokengithub:read |
| Detect build configuration in a repository. | github.detectStack(input) | GET /api/github/repos/:owner/:repo/detectgithub:read |
| List repository files at a path. | github.listFiles(input) | GET /api/github/repos/:owner/:repo/filesgithub:list |
| Read a repository tree. | github.listTree(input) | GET /api/github/repos/:owner/:repo/treegithub:list |
| Read repository file content. | github.getFile(input) | GET /api/github/repos/:owner/:repo/filegithub:read |
Webhooks
| Operation | SDK | REST API |
|---|---|---|
| List repository webhooks. | github.listWebhooks(input) | GET /api/github/repos/:owner/:repo/webhooksgithub:list |
| Register a repository webhook for deployment events. | github.registerWebhook(input) | POST /api/github/repos/:owner/:repo/webhooksgithub:write |
| Delete a repository webhook. | github.deleteWebhook(input) | DELETE /api/github/repos/:owner/:repo/webhooksgithub:admin |
GitHub App sources
| Operation | SDK | REST API |
|---|---|---|
| List configured GitHub App sources. | github.listSources() | GET /api/github/sourcesgithub:admin · Self-hosted |
| Start GitHub App manifest registration. | github.beginManifest(input) | POST /api/github/sources/manifestgithub:admin · Self-hosted |
| Convert a completed GitHub App manifest. | github.convertManifest(input) | POST /api/github/sources/manifest/convertgithub:admin · Self-hosted |
| Register an existing GitHub App source. | github.createManualSource(input) | POST /api/github/sources/manualgithub:admin · Self-hosted |
| Change a GitHub App source’s configuration. | github.updateSource(id, input) | PATCH /api/github/sources/:idgithub:admin · Self-hosted |
| Verify source credentials with GitHub. | github.verifySource(id) | POST /api/github/sources/:id/verifygithub:admin · Self-hosted |
| Select the default GitHub App source. | github.setDefaultSource(id) | POST /api/github/sources/:id/defaultgithub:admin · Self-hosted |
| Create an installation URL for a GitHub App source. | github.createInstallUrl(id) | POST /api/github/sources/:id/installgithub:admin · Self-hosted |
| Remove a GitHub App source. | github.deleteSource(id) | DELETE /api/github/sources/:idgithub:admin · Self-hosted |
HTTP endpoints
| Operation | SDK | REST API |
|---|---|---|
| GitHub OAuth callback — browser navigates here, no session yet. | HTTP only | GET /api/github/connect/redirectHandler authentication |