> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nt3.io/llms.txt
> Use this file to discover all available pages before exploring further.

# GitHub App setup (self-hosted)

> Register your own GitHub App so a self-hosted Entri instance can sync translations into GitHub repositories.

The [GitHub integration](https://docs.nt3.io/platform/features/github-integration.md) is powered by a GitHub App that **you** register for your Entri instance. This page walks through creating the App, choosing permissions, and wiring its credentials into your deployment.

> [!NOTE]
>
> The integration is an **org-level connection with a per-project repository**: one GitHub App installation per Entri organization, and each project picks one installed repository, a branch, and a sync mode. File paths and formats are read from the repository's `.nt3.yml` (the same file `nt3 pull` uses).

Until all the variables below are set, the GitHub integration stays hidden in the UI (`githubAppEnabled: false` in `GET /api/v1/config`).

## 1. Create the GitHub App

Go to **GitHub → Settings → Developer settings → GitHub Apps → New GitHub App**, or create it under your **organization's** settings if it should be organization-owned.

### Basic information

* **Name:** anything, e.g. `Entri Sync`. The URL slug GitHub derives from it becomes `GITHUB_APP_SLUG`.
* **Homepage URL:** your Entri instance, e.g. `https://entri.example.com`.

### Identifying and authorizing users

> [!WARNING]
>
> Enable **"Request user authorization (OAuth) during installation"**. GitHub then sends an OAuth `code` to Entri after installation. Entri exchanges it for a user token and checks that the installation is among the installations **that GitHub user can access**, so nobody can bind an installation they don't have access to. Without OAuth during installation, GitHub sends no `code` and every connection fails with `missing_code`.

If connecting fails, Entri returns to the organization settings with a `reason`:

| Reason | Meaning |
| - | - |
| `missing_code` | GitHub sent no OAuth `code`: enable "Request user authorization (OAuth) during installation". |
| `oauth_exchange_failed` | The `code` could not be exchanged for a user token (the `detail` holds the HTTP status). A 401 usually means `GITHUB_APP_CLIENT_ID` / `GITHUB_APP_CLIENT_SECRET` don't belong to the App named by `GITHUB_APP_SLUG`. |
| `installation_not_visible` | The authorizing GitHub user cannot access this installation, for example because it was installed on an organization they are not a member of. |
| `ownership_check_failed` | Any other error while verifying the installation (for example GitHub being unreachable). Check the Entri log. |
| `invalid_state` / `state_expired` | The install link was tampered with or older than 10 minutes, or was already used. Start again from Entri. |
| `org_mismatch` | You switched the active organization in Entri during the install. |
| `forbidden` | Only organization owners and admins can connect GitHub. |
| `approval_required` | A GitHub organization owner must approve the installation request first. |
| `cancelled` | The installation was cancelled on GitHub. |

* **Callback URL:** `https://<your-instance>/api/v1/integrations/github/setup-callback`
  * For local development: `http://localhost:3333/api/v1/integrations/github/setup-callback`
* **Setup URL:** leave blank. Because OAuth during installation is enabled, the callback URL handles the post-install redirect.
* **Redirect on update:** leave unchecked unless you want reconfiguration to re-run the callback. The callback handles `setup_action=update` either way.

### Webhook

* Uncheck **Active**. Entri does not use a webhook receiver; an uninstalled or suspended installation is detected the next time Entri requests an installation token.

### Repository permissions

Grant exactly these (least privilege):

| Permission | Access |
| - | - |
| **Contents** | Read & write |
| **Pull requests** | Read & write |
| **Metadata** | Read-only (mandatory, selected automatically) |

Do **not** grant `Workflows`, `Actions`, `Administration`, or `Secrets`. Lacking `Workflows` is a second line of defense behind Entri's own `.github/` path denylist.

### Where can this App be installed?

"Only on this account" is fine for a single organization. Choose "Any account" if other GitHub organizations will install it. When installing, we recommend **Only select repositories** rather than all repositories.

## 2. Collect the credentials

From the App's settings page:

| Environment variable | Where to find it |
| - | - |
| `GITHUB_APP_ID` | "App ID" near the top of the settings page |
| `GITHUB_APP_SLUG` | The slug in the App URL: `github.com/apps/<slug>` |
| `GITHUB_APP_CLIENT_ID` | "Client ID" |
| `GITHUB_APP_CLIENT_SECRET` | "Client secrets" → **Generate a new client secret** |
| `GITHUB_APP_PRIVATE_KEY` | "Private keys" → **Generate a private key** (downloads a `.pem` file) |
| `GITHUB_STATE_SECRET` | Generate it yourself (see below) |

Generate a dedicated state secret, used to sign the install `state` parameter. Do **not** reuse `ENCRYPTION_KEY`:

```bash theme={null}
openssl rand -hex 32
```

Optionally set `GITHUB_BOT_EMAIL` to control the committer email of sync commits (default: `noreply@<hostname of APP_HOST>`).

> [!NOTE]
>
> These are separate from `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET`, which enable "Sign in with GitHub". Don't overwrite those.

## 3. Configure your instance

Set the six variables in your deployment (Docker Compose `.env`, Helm values / Kubernetes Secret, or your platform's secret store). `ENCRYPTION_KEY` must also be set, and `APP_HOST` must point at the public URL of the web app, because the callback redirects the browser there after installation.

### Private key formatting

The `.pem` file is multi-line. In a `.env` file, store it as a single line with literal `\n` escapes, wrapped in double quotes:

```bash theme={null}
# Convert the downloaded PEM into a single-line value
awk 'BEGIN{ORS="\\n"} {print}' your-app.private-key.pem
```

```bash theme={null}
GITHUB_APP_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----\n"
```

Entri converts literal `\n` back into real newlines. In Kubernetes, you can instead mount the PEM contents as-is in a Secret value. If token requests fail with a key-parse error after deploying, the newlines did not survive your secret store.

> [!TIP]
>
> A single App can list several callback URLs, but the cleanest setup is **one App per environment** (for example `entri-dev` and `entri`), so a local install never lands on your production instance.

## 4. Verify end to end

1. Create a test repository containing a valid `.nt3.yml` (run `nt3 init` locally and commit it). Make sure `source_language` matches the Entri project's source language.
2. In Entri, go to **Settings → Organization → GitHub → Connect GitHub** and install the App on the test repository. You are redirected back with a success message.
3. In **Settings → Project → GitHub Sync**, select the repository, set the branch, choose a mode, and save.
4. Click **Sync now** and check:
   * **Commit mode:** a new bot commit appears on the branch with the expected files.
   * **Pull request mode:** an `nt3/sync-*` branch and an open PR appear; re-running updates the same PR.
   * **No changes:** re-running reports "Up to date" without creating an empty commit or PR.
   * **Protected branch in commit mode:** a clear error suggests using PR mode.
5. Disconnect from the organization settings and confirm the connection is removed.

### Common failures

| Symptom | Likely cause |
| - | - |
| `ownership_check_failed` after install | OAuth during installation is not enabled, or the callback URL is wrong |
| 422 mentioning `.nt3.yml` | The file is missing on the configured branch |
| `409 Conflict` | A sync for that project is already running |
| Key-parse error when syncing | `GITHUB_APP_PRIVATE_KEY` newlines were mangled |

## Known limitations

* Sync is **additive / overwrite-only**: stale translation files are never deleted from the repository.
* Sync is **triggered manually** (no webhook or schedule yet).
* Importing from GitHub into Entri is not supported; use `nt3 push` for that direction.
* Commits show as **Unverified**, which is normal for GitHub App tokens.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.