neosourceDocs
Search docs

Connect a GitHub App installation that was started on GitHub

POST/api/github/app/installations/{installation_id}/claim

claimGithubAppInstallation

The explicit step for an installation the callback could not record: one made from `github.com/apps/{slug}` rather than from `POST …/consent`, so it arrived with no `state`. The callback sends the browser to `/settings/integrations/github?claim=…` and this is the request that page makes when the user says yes. Runs exactly the ownership chain the consent callback runs after consuming its nonce: the installation must be **this App's** (an installation token mints against it), it must be on **the caller's own linked GitHub user account**, and it must not already be connected to another neosource account. Nothing is written when any of those fail. Refusals the SPA acts on carry their own `error` kind rather than the coarse `forbidden`: `github_identity_required` (403) — this account has no linked GitHub identity, so there is nothing to prove the installation is yours against; link GitHub, then claim again. `org_installations_unsupported` (403) — the installation is owned by a GitHub *organisation*, which this deployment cannot yet prove is yours (see the callback's description); no action by the caller changes it. `conflict` (409) — already connected to a different neosource account; uninstall the App on GitHub and install it again, or sign in as that account. `forbidden` (403) — a personal installation on somebody else's GitHub account, or a request made with an API token. `upstream` (502) — GitHub would not describe the installation, which is also what an id that is not this App's looks like. **Browser sessions only**, for the same reason as the callback: this associates a durable third-party credential with an account, and a leaked scoped token must not be able to do that. 404 when this deployment has no GitHub App configured.

Requires authentication using a session cookie — see tokens and scopes.

curl

curl -X POST 'https://neosource.dev/api/github/app/installations/INSTALLATION_ID/claim' \
  -b 'ns_session=$NEOSOURCE_SESSION'

fetch

fetch("https://neosource.dev/api/github/app/installations/INSTALLATION_ID/claim", {
  method: "POST",
  credentials: "include",
});

Path parameters

installation_idrequired

GitHub's installation id, as the callback's `?claim=` carried it.

integerint64

Responses

200The installation, now connected to the caller's account

application/json

GithubAppInstallationSummary

object

One App installation this account consented to.

account_loginrequired

string

The GitHub user or organization the App is installed on.

account_typerequired

GithubInstallationAccountType

string

Whether an installation sits on a GitHub user or an organization.

"user""organization"

app_installation_idrequired

GithubInstallationId

integerint64

GitHub's App installation id (`installation.id` on every App webhook). Nothing is rejected at the type level; the `> 0` invariant is a database CHECK, because the only values that reach here come from GitHub.

created_atrequired

integerint64

Epoch milliseconds.

last_seen_at

integer | nullint64

Epoch milliseconds of the last time GitHub told us anything about this installation. `null` until the first observation after the callback.

repository_selectionrequired

GithubRepositorySelection

string

Whether the installation covers every repository on the account or an explicitly selected subset (`installation.repository_selection`).

"all""selected"

staterequired

GithubInstallationState

string

Lifecycle of an App installation (`github_app_installations.state`). `Deleted` is a tombstone, not a row removal: an uninstall must stay observable so a later redelivery has something to key on.

"active""suspended""deleted"

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_identity_required, org_installations_unsupported
  • 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