Docs
Search docs

List the authenticated user's GitHub repositories

GET/api/github/repos

listGithubRepos

Lists repos from the caller's linked GitHub identity for the import picker. Which listing runs depends on the identity's `token_kind`. Under a GitHub **App** user-to-server token the list is every repository of every App installation the user can see (`installations` names them, grouped by `account_login`); such a token has no scopes, so `can_list_private` is always `true`, a `visibility` filter is applied to the result rather than asked of GitHub, and the way to widen the list is to install the App on more repositories — `install_url`. Under a classic OAuth token (or a link that pre-dates the kind) it is the account's own `/user/repos`: with no `visibility` param it returns the widest view the granted scope allows — private repos included once the identity carries the `repo` scope, public-only before that — and reports which via `can_list_private`; passing `visibility=private`/`all` without the scope is a `403 NeedsScopeError`. On either path a token locked to specific repositories or workspaces never gains private enumeration: an unqualified request stays public-only, and an explicit `private`/`all` from such a token is `403 forbidden` on the App path.

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

curl

curl -X GET 'https://neosource.dev/api/github/repos' \
  -H 'Authorization: Bearer $NEOSOURCE_TOKEN'

fetch

fetch("https://neosource.dev/api/github/repos", {
  method: "GET",
  headers: {
    Authorization: "Bearer $NEOSOURCE_TOKEN",
  },
});

Query parameters

visibility

One of `public`, `private`, `all`. Omitted means **auto**: the widest view the granted scope allows — `all` when the linked identity carries the `repo` scope (Tier 2), `public` otherwise. Asking for `private`/`all` *explicitly* is a demand rather than a preference, so it returns `403 NeedsScopeError` when the scope is missing instead of silently degrading to public-only.

string

Responses

200GitHub repositories

application/json

GithubRepoListResponse

object

app_connections_url

string | null

Where the user manages *this deployment's* OAuth app on GitHub — `https://github.com/settings/connections/applications/{client_id}`. That page is the only place an organization's access can be granted (as an owner) or requested (as a member), which is what makes a whole organization's repositories missing from `repos` fixable. Absent when GitHub is not configured on the deployment, so the SPA renders the affordance from the response rather than guessing. This is not a scope signal and cannot be one: GitHub does not enumerate the organizations an unapproved OAuth app is restricted from, so the picker cannot name them — it can only point at the page that lists them.

can_list_privaterequired

boolean

Whether `repos` was allowed to include private repositories. On the GitHub App path this is `true` for a session or an unrestricted token: reach is decided by where the App is installed, and every repository an installation grants is listed whatever its visibility. On the classic OAuth path it means the linked identity's token carries the `repo` scope; `false` there is public-only *by scope, not by choice*. On either path a token locked to specific repositories or workspaces reports `false` and sees public repositories only. Lets the SPA render the right empty/CTA state from the list response alone, with no second round trip to read granted scopes.

install_url

string | null

Where the GitHub App is installed — `https://github.com/apps/{slug}/installations/new`. Present whenever this deployment has an App configured, on both paths: it is the one affordance that widens what a user-to-server token can reach, and the way a classic-token user moves onto the App. Absent when no App is configured, so the SPA never links somewhere that would 404.

installations

array

The App installations the linked identity can see (GitHub App path only; always empty on the classic path, and empty for a resource-locked token, whose lock also withholds the account's installation set — org membership can be private). An installation that appears here with nothing in `repos` under its `account_login` is one whose repository selection excludes everything — the "add repositories" case, fixed at `install_url`.

items

GithubInstallationSummary

object

One GitHub App installation the linked identity can see, as `GET /user/installations` reports it. Only present on the GitHub App path of the picker (a user-to-server token); the classic path has no installations to speak of. Distinct from `GithubAppInstallationSummary`, which describes an installation this deployment has CONSENTED to and stored — with a state and a created-at of our own. This is GitHub's answer, uncommitted, for grouping the picker and pointing "add repositories" at the right owner.

account_loginrequired

string

The GitHub user or organization the App is installed on — the `owner_login` its repositories carry, which is the grouping key.

account_typerequired
GithubInstallationAccountType
installation_idrequired

integerint64

GitHub's installation id.

repositories_unavailable

boolean

GitHub refused to list this installation's repositories (403/404 on `GET /user/installations/{id}/repositories` — suspended, uninstalled between two calls, or a permission the App lost there). The installation is still reported and the rest of the list is intact; its own repositories are simply absent from `repos`.

repository_selectionrequired
GithubRepositorySelection
provider_token_kind
one of
  • ProviderTokenKind

    string

    Which kind of GitHub credential an identity's stored access token is — the fact every scope-shaped gate has to branch on now that production login runs on the GitHub **App**'s client id. A classic OAuth App token (`gho_…`) carries the scopes the user consented to, so `access_token_scopes` says what it can reach. A GitHub App user-to-server token (`ghu_…`) carries NO scopes at all — the token response's `scope` is empty and the `scope` query parameter is ignored — and reaches exactly the repositories where the App is installed and the user has access. A gate that reads scopes off such a token 403s forever, which is what this enum exists to make unrepresentable: every writer of an identity row states the kind, and every reader branches on it before it looks at a scope string. Stored as `account_identities.token_kind`; `NULL` there is a link made before the column existed, which is by construction a classic token.

    "oauth_app""github_app_user"

  • null

reposrequired

array

items

GithubRepoSummary

object

A GitHub repo as offered in the import picker. Mirrors the trimmed `neosource_engine::github_api::GithubRepo` (mapped in the route — DTOs never depend on the engine).

default_branch

string | null

description

string | null

full_namerequired

string

`owner/name` — the import addressing key.

html_url

string | null

The repo's page on github.com, so the picker can link a row out to its source. Server-supplied rather than built client-side from `full_name`: the URL's host is a fact about the provider, not something the SPA should hard-code.

language

string | null

namerequired

string

owner_loginrequired

string

privaterequired

boolean

size_kbrequired

integerint64

Disk size in KiB, as GitHub reports it.

403Forbidden — one of: forbidden, needs_scope

application/json

one of
  • ErrorForbidden
  • NeedsScopeError

    object

    `403` body returned when a classic OAuth token is asked for something its granted scopes do not cover — an explicit private listing without `repo`, or the one-click GitHub mirror without `admin:public_key`. `scope` names the missing grant so the client can say exactly what the token lacks. There is no elevation route to widen it any more (a re-link goes through the GitHub App, whose tokens carry no scopes), so the client's answer is to say so, never to start a handshake.

    errorrequired

    string

    Always `needs_scope` — this body exists to carry the extra fields that kind needs.

    "needs_scope"

    scoperequired

    string

    The provider scope to request via elevation (e.g. `"repo"`).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 401Authentication required
  • 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

Schemas

Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.

GithubInstallationAccountType

string

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

"user""organization"

GithubRepositorySelection

string

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

"all""selected"