---
title: "Bind a GitHub installation to the organization (owner/admin)"
method: POST
path: "/orgs/{org_id}/integrations/github/connect/complete"
tags: ["Organizations"]
---

# Bind a GitHub installation to the organization (owner/admin)

`POST /orgs/{org_id}/integrations/github/connect/complete`

## Request

- Base URL: `https://api.mnemom.ai/v1`
- URL: `https://api.mnemom.ai/v1/orgs/{org_id}/integrations/github/connect/complete`
- Auth: HTTP bearer

Verifies the sealed `state`, fetches the installation from GitHub with the App credential, and binds it to this org. Each `state` is single-use: it is spent on the first `installation_id` it is presented with (a retry of the same org + installation is idempotent). Every refusal after the state checks out answers the same `404 installation_not_found` — an unknown installation, another GitHub App's, one that is or ever was bound to another Mnemom org (a disconnect leaves the GitHub installation live, so it never moves by id; uninstall on GitHub and install afresh), a NEW binding for an installation created before this flow's `state` was minted, or a state already spent on a different installation. Install the App from the `install_url` this flow returned. When the API runs with `GITHUB_INSTALL_OAUTH_REQUIRED=true`, the body must also carry the install-time OAuth `code` (the App requests user authorization during installation; the setup redirect forwards it): it is exchanged for a user-to-server token and the installation must appear in that user's `GET /user/installations`. A missing code or a failed check is the same `404 installation_not_found`; with the flag on but the App client credentials unset the route answers `503 github_app_unconfigured`.

## Path parameters

- `org_id` string, required

## Request body

- object
  - `state` string, required
  - `installation_id` integer, required
  - `code` string: Install-time GitHub OAuth code, forwarded by the setup redirect. Required only when the API enforces the installer OAuth proof; ignored otherwise. Never stored.

## Response `200`

Re-bound this org's own existing (disconnected) installation row

- object
  - `connected` true, required
  - `app_slug` string
  - `installation` object, required
    - `installation_id` integer, required
    - `account_login` string, required
    - `account_id` integer
    - `account_type` string
    - `repository_selection` string, required: GitHub's `all` | `selected`.
    - `permissions` object
    - `connected_by` string
    - `connected_at` string, date-time
    - `suspended_at` string, date-time, nullable
    - `suspended` boolean, required

## Other responses

- `201`: Installation bound
- `400`: Bad request
- `401`: Authentication required
- `403`: Forbidden
- `404`: Resource not found
- `429`: Rate-limit exceeded. The global per-IP limiter (100 requests/minute, applied to every `/v1/*` route) rejected this request. Back off until the window resets — `Retry-After` carries the cooldown in seconds and `X-RateLimit-Reset` the absolute reset time.
- `500`: Server error — request was well-formed but the server failed to fulfill it. Typically a downstream dependency (DB / RPC / external API) returned an unexpected error.
- `502`: An upstream dependency returned an invalid response.
- `503`: An upstream dependency is currently unavailable. The request was not processed; retry after the cooldown window.

---

[API](https://skmtc.dev/mnemom/apis/mnemom-api.md) · [All operations](https://skmtc.dev/mnemom/apis/mnemom-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mnemom/apis/mnemom-api/revisions/274b7a6ea182?raw)
