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. Four locks, all before any row is written: the credential must have **account reach**; the caller must hold repo **admin**; the installation must be one **this account** connected; 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. An installation this account did not connect is `403` whether or not it exists — the difference would be an existence oracle. **The `repo:write` scope alone is not enough.** A resource-locked token (scoped to one repository or one workspace) and a token carrying only `repo:*` are both refused with `403`, even for a caller who is a repository admin. The binding is durable, account-scoped state — it is decided against *which installations this account connected* — so the credential must also reach the account surface (`user:read`), which a resource-locked token deliberately does not. A session or an unrestricted token 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`. 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 '{"app_installation_id":"string","github_repository_id":"string"}'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({"app_installation_id":"string","github_repository_id":"string"}),
});Path parameters
ownerrequiredRepository owner or organization slug.
reporequiredRepository name.
Request bodyrequired
application/json
Responses
201Binding created
application/json
403Forbidden — one of: forbidden, needs_scope
application/json
Standard errors
Bodies documented once for the whole API — see standard errors.
- 400Bad Request — one of: invalid_input
- 401Authentication required
- 404Not Found — one of: not_found
- 409Conflict — one of: already_exists, conflict
- 423Locked — one of: busy
- 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