GitHub App installation callback
GET/api/github/app/installations/callback
githubAppInstallationCallback
Where GitHub sends the browser after an App installation. Consumes the single-use `state` nonce, proves the installation belongs to this App by minting an installation token against it, proves it belongs to **the caller** — the installation must be on their own linked GitHub user account — and only then records it against the consenting account. **Only user-owned installations are supported in this slice.** An installation owned by a GitHub *organisation* is refused with `org_installations_unsupported`: the only endpoint that can prove an org installation is a particular person's (`GET /user/installations`) answers solely to a user access token minted by the GitHub App itself, and this deployment's login flow mints classic OAuth App tokens instead. Admitting one anyway would hand its ownership to whoever completed a callback first, so it fails closed until login moves onto the App. **Browser sessions only.** A PAT or workspace token is refused: this route associates a durable third-party credential with an account, and a leaked scoped token must not be able to do that. **An installation started on GitHub arrives with no `state`.** Anyone can install a public App from `github.com/apps/{slug}`; that callback carries `installation_id` and `setup_action=install` and nothing this server minted. Nothing is written and nothing is asked of GitHub — the browser is sent to `/settings/integrations/github?claim={installation_id}`, signed in or not, where the user connects it explicitly through `POST /api/github/app/installations/{installation_id}/claim`. A callback with neither `state` nor `installation_id` is `?error=bad_request`. Always answers `303`, never a JSON body — on success to the consent's `return_to` (or `/settings/integrations/github`), on failure to the same location with `?error=` set to one of `invalid_state`, `forbidden`, `unauthenticated`, `bad_request`, `already_connected`, `github_identity_required`, `org_installations_unsupported`, `upstream`. `already_connected` means the installation is bound to a different neosource account and retrying will not help; `github_identity_required` means this account has no linked GitHub identity, so there is nothing to prove the installation is yours against — link GitHub, then install the App again; `org_installations_unsupported` is the organisation case above, and no action by the caller changes it. The `state` value is never echoed back.
Authentication optional — this operation also accepts anonymous requests. When authenticating, use a session cookie.
curl
curl -X GET 'https://neosource.dev/api/github/app/installations/callback' \
-b 'ns_session=$NEOSOURCE_SESSION'fetch
fetch("https://neosource.dev/api/github/app/installations/callback", {
method: "GET",
credentials: "include",
});Query parameters
installation_idGitHub's installation id.
stateThe nonce this server minted at consent time.
setup_action`install` or `update`, per GitHub. **Accepted and ignored**: nothing branches on it server-side and it is not forwarded in the redirect — the SPA reads the resulting installation list, which states the same fact without trusting a query parameter. Declared so the callback's `Query` extractor does not have to be lenient about it.
Responses
303Redirect back into the app, with `?error=` on failure
404No GitHub App is configured on this deployment
Standard errors
Bodies documented once for the whole API — see standard errors.
- 429Rate limited — retry after the `Retry-After` header
- 500Internal server error
- 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
- 504Gateway timeout — the request exceeded the server's handling budget