neosourceDocs
Search docs

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_id

GitHub's installation id.

integerint64

state

The nonce this server minted at consent time.

string

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.

string

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