# GitHub

Private repositories are reached through a GitHub App installation, linked to a Light Cloud organisation. One GitHub account can be linked to several organisations, and one organisation can hold several installations.

These endpoints are mostly `GET` and take their parameters in the path or query string, unlike the rest of the API.

## Check repository access

```endpoint
GET /api/github-app/installation-status
> Whether a specific repository is reachable. `repoAccess: false` with `installed: true` means the App is on the account but was not granted this repository.
permission: read:projects

param organisationId | string | required | Query parameter
param owner | string | required | Query parameter — repository owner
param repo | string | required | Query parameter — repository name

response 200 | Installation and access state
{
  "configured": true,
  "installed": true,
  "installationId": 12345678,
  "accountLogin": "acme",
  "repoAccess": true
}

code curl
curl -G https://api.light-cloud.com/api/github-app/installation-status \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  --data-urlencode "organisationId=org_1Ab" \
  --data-urlencode "owner=acme" \
  --data-urlencode "repo=shop"

code Node
const params = new URLSearchParams({
  organisationId: "org_1Ab",
  owner: "acme",
  repo: "shop",
});

const res = await fetch(
  `https://api.light-cloud.com/api/github-app/installation-status?${params}`,
  { headers: { Authorization: `Bearer ${sessionToken}` } }
);

const status = await res.json();
if (!status.repoAccess) {
  console.log("Grant this repository to the Light Cloud App on GitHub");
}

code Python
import requests

res = requests.get(
    "https://api.light-cloud.com/api/github-app/installation-status",
    headers={"Authorization": f"Bearer {session_token}"},
    params={"organisationId": "org_1Ab", "owner": "acme", "repo": "shop"},
)
res.raise_for_status()

status = res.json()
if not status["repoAccess"]:
    print("Grant this repository to the Light Cloud App on GitHub")
```

## Start an installation

```endpoint
GET /api/github-app/install
> Returns the GitHub URL to send a person to. GitHub returns them to /api/github-app/callback, which links the installation.
permission: read:projects

response 200 | Where to send the user
{ "url": "https://github.com/apps/light-cloud/installations/new?state=..." }

code curl
curl https://api.light-cloud.com/api/github-app/install \
  -H "Authorization: Bearer $SESSION_TOKEN"

code Node
const res = await fetch("https://api.light-cloud.com/api/github-app/install", {
  headers: { Authorization: `Bearer ${sessionToken}` },
});

const { url } = await res.json();
// Open `url` in the browser; GitHub redirects back when the App is installed
```

## List repositories

```endpoint
GET /api/github-app/organisation/:orgId/repositories
> Every repository the organisation's linked installations can reach.
permission: read:projects

param orgId | string | required | Path parameter — the organisation

response 200 | Repositories
[
  {
    "id": 45678901,
    "name": "shop",
    "full_name": "acme/shop",
    "private": true,
    "default_branch": "main"
  }
]

code curl
curl https://api.light-cloud.com/api/github-app/organisation/org_1Ab/repositories \
  -H "Authorization: Bearer $SESSION_TOKEN"

code Node
const res = await fetch(
  `https://api.light-cloud.com/api/github-app/organisation/${organisationId}/repositories`,
  { headers: { Authorization: `Bearer ${sessionToken}` } }
);

const repositories = await res.json();
repositories.forEach((repo) => console.log(repo.full_name, repo.default_branch));

code Python
import requests

res = requests.get(
    f"https://api.light-cloud.com/api/github-app/organisation/{organisation_id}/repositories",
    headers={"Authorization": f"Bearer {session_token}"},
)
res.raise_for_status()

for repo in res.json():
    print(repo["full_name"], repo["default_branch"])
```

## List branches

```endpoint
GET /api/github-app/organisation/:orgId/repositories/:owner/:repo/branches
> Branches of one repository. For a public repository you do not need an installation — use POST /api/applications/public-branches instead.
permission: read:projects

param orgId | string | required | Path parameter — the organisation
param owner | string | required | Path parameter — repository owner
param repo | string | required | Path parameter — repository name

response 200 | Branches
[
  { "name": "main", "commit_sha": "9f3c1ab" },
  { "name": "develop", "commit_sha": "2b8e004" }
]

code curl
curl https://api.light-cloud.com/api/github-app/organisation/org_1Ab/repositories/acme/shop/branches \
  -H "Authorization: Bearer $SESSION_TOKEN"

code Node
const res = await fetch(
  `https://api.light-cloud.com/api/github-app/organisation/${organisationId}` +
    `/repositories/acme/shop/branches`,
  { headers: { Authorization: `Bearer ${sessionToken}` } }
);

const branches = await res.json();
```

## Check several accounts

```endpoint
POST /api/github-app/organisation/:orgId/check-accounts
> Which of these GitHub accounts have the App installed, and which are linked to this workspace. `installed: true, linkedToThisOrg: false` is the case that trips people up — the App is on that account, but this workspace cannot use it until it is linked.
permission: read:projects

param orgId | string | required | Path parameter — the organisation
param accountLogins | string[] | required | GitHub account logins to check

response 200 | Per-account state
{
  "accounts": {
    "acme": { "installed": true, "linkedToThisOrg": true },
    "someone-else": { "installed": true, "linkedToThisOrg": false }
  }
}

code curl
curl -X POST https://api.light-cloud.com/api/github-app/organisation/org_1Ab/check-accounts \
  -H "Authorization: Bearer $SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"accountLogins": ["acme", "someone-else"]}'

code Node
const res = await fetch(
  `https://api.light-cloud.com/api/github-app/organisation/${organisationId}/check-accounts`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${sessionToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ accountLogins: ["acme", "someone-else"] }),
  }
);

const { accounts } = await res.json();
```

## Managing installations

| Endpoint | Description |
| --- | --- |
| `GET /api/github-app/installations` | Every installation visible to the user |
| `GET /api/github-app/organisation/:orgId/installations` | Those linked to this organisation |
| `POST /api/github-app/organisation/:orgId/link-installation` | Attaches an existing installation to this organisation |
| `POST /api/github-app/organisation/:orgId/sync-installations` | Re-reads GitHub and prunes rows whose installation is gone |
| `DELETE /api/github-app/organisation/:orgId/installations/:installId` | Unlinks it from this organisation |
| `DELETE /api/github-app/organisation/:orgId/installations/:installId/uninstall` | Unlinks **and** removes the App from the GitHub account |
| `GET /api/github-app/organisation/:organisationId/stream` | Server-sent events, so a page can react as an installation completes |

## GitLab and Bitbucket

The same shape exists under `/api/gitlab` and `/api/bitbucket` (connections instead of installations: `GET /organisation/:orgId/connections`, `POST /organisation/:orgId/link-connection`, `DELETE /organisation/:orgId/connections/:id`, `POST /disconnect`, and the same repository and branch listings), each behind a feature flag. Check `GET /api/config/feature-flags` before relying on them.

## Related

- [Applications](/reference/api/applications): Creating an application from a repository.
- [Git automation](/create/git-automation): Deploy on push, branch rules, and stale environments.
- [Deployments](/platform/deployments): What happens after a push.
