Bind a repository to a GitHub App installation
POST/api/repos/{owner}/{repo}/github-installation
bindGithubInstallation
Pins this repository to a GitHub App installation and to GitHub's **immutable numeric** repository id. A rename or transfer upstream moves the `owner/name` locator and keeps the id, which is why the id is what is stored. Three locks, all before any row is written: the caller must hold repo **admin**; the caller must be allowed to use the installation; and the repository must really be inside that installation, proved by a single `GET /repositories/{id}` under the installation's own token. A failure at any of them writes nothing. Who may use an installation depends on what it is installed on. A **user** installation is usable by the account that connected it. An **organisation** installation is usable by an Admin or Owner of the organisation workspace its org claim created, and only for that workspace's own repositories — the policy layer's `workspace:integration:manage` decision on the repository's workspace. Any other case — an installation nobody connected, someone else's, an org installation on a repo outside the org, a workspace Member, an insufficient token — is one and the same `403`, whether or not the installation exists: the difference would be an existence oracle. **The `repo:write` scope alone is not enough.** For a user installation the credential must also reach the account surface (`user:read`, no resource lock), because the binding is decided against *which installations this account connected*. For an organisation installation the credential must be allowed the workspace action on the repository's workspace — so a token locked to a repository, locked to another workspace, or carrying only `repo:*` is refused. A session or an unrestricted token of an account that may passes. Idempotent on the exact same triple. Binding a repository that is already bound to a *different* GitHub repository is `409` — the provider identity is immutable — as is binding a GitHub repository that another repository already claims. Health starts `unverified`. **Both ids or neither** (one without the other is `400`). With neither, the server finds them — "Connect GitHub App": the GitHub repository is this repository's GitHub relationship (`400` when it has none), the installation is one you may use that reaches it (the same per-installation decision as above, asked before any token is minted for it), and the id is GitHub's answer for it under that installation's token. `403 github_app_install_required` (its `message` carries the install URL) when no such installation exists. The pair found is then bound through the same three locks. 404 when this deployment has no GitHub App configured.
Requires authentication using a bearer token or a session cookie — see tokens and scopes.
curl
curl -X POST 'https://neosource.dev/api/repos/OWNER/REPO/github-installation' \
-H 'Authorization: Bearer $NEOSOURCE_TOKEN' \
-H 'Content-Type: application/json' \
-d '{}'fetch
fetch("https://neosource.dev/api/repos/OWNER/REPO/github-installation", {
method: "POST",
headers: {
Authorization: "Bearer $NEOSOURCE_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});Path parameters
ownerrequiredRepository owner or organization slug.
reporequiredRepository name.
Request bodyrequired
application/json
Responses
201Binding created
application/json
Standard errors
Bodies documented once for the whole API — see standard errors.
- 400Bad Request — one of: invalid_input
- 401Authentication required
- 403Forbidden — one of: forbidden, github_app_install_required
- 404Not Found — one of: not_found
- 409Conflict — one of: conflict
- 429Rate limited — retry after the `Retry-After` header
- 500Internal server error
- 502Bad Gateway — one of: upstream
- 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
- 504Gateway timeout — the request exceeded the server's handling budget