# OAuth & Connected Apps

Authorize third-party tools to act on your behalf in Korey using the OAuth 2.0 authorization code flow.

Korey supports [OAuth 2.0](https://datatracker.ietf.org/doc/html/rfc6749) so third-party tools — editor extensions, scripts, CI jobs — can ask for your permission before accessing the Korey API on your behalf. No passwords are shared, and you can revoke access at any time.

## How it works

The standard [authorization code flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1) is used:

1. The tool redirects you to Korey's authorization page (`/oauth/authorize`).
2. You review the requested **scopes** and click **Approve**.
3. Korey redirects back to the tool with an authorization code.
4. The tool exchanges the code for an **access token** (and optionally a **refresh token**).
5. The tool calls the Korey API using the access token as a `Bearer` token.

Access tokens are short-lived. The tool uses the refresh token to obtain new access tokens automatically without asking you to approve again.

## Scopes

Scopes control what a connected app can do. Korey follows a least-privilege model — tools should request only what they need.

| Scope | What it allows |
|---|---|
| `threads:read` | List and read all threads visible to the user, stream AI responses |
| `threads:read:own` | Read only threads owned by the token's user — no access to other users' threads |
| `threads:write` | Create threads, send messages, and submit feedback |

Scopes are **explicit and composable** — `threads:write` does not imply `threads:read`. A client that needs both must request both. For example, a client that only posts messages but never lists threads can request `threads:write` alone.

The `/me` endpoint (your profile and workspace membership) requires no scope — any valid token can call it.

  When a tool requests more scopes than it needs, you can still approve — but consider whether a narrower set is appropriate. You can always revoke access later.

## Admin: OAuth client management

Org admins can view and manage all OAuth clients registered in the workspace from **Settings → Organization → OAuth Clients**.

The table shows each client's name, owner, redirect URIs, and when it was last used. Admins can:

- **View client details** — click a row to open the detail panel showing client ID, redirect URIs, logo, and metadata.
- **Rotate the client secret** — invalidates the existing secret and generates a new one (confidential clients only). The new secret is shown once.
- **Delete a client** — permanently removes the client and revokes all associated tokens. Any integrations using that client will stop working immediately.
- **Generate a registration token** — issue a one-time token so an OAuth client can self-register programmatically.

  Rotating the client secret immediately invalidates the old one. Update your application's configuration before rotating or it will lose API access.

## Connected apps (for users)

To see which apps have been authorized and revoke any of them:

1. Open **Settings → Connected Apps**.
2. Each row shows the app name, the scopes it holds, and when it last used its token.
3. Click **Revoke** and confirm the dialog. The app's tokens are invalidated immediately and it will need to ask for your permission again the next time it needs access.

## OAuth clients (for developers)

If you're building an app that integrates with Korey, you need to register an OAuth client:

1. Open **Settings → OAuth Clients**.
2. Click **Create client**.
3. Fill in the form:
   - **Name** — shown to users on the consent screen.
   - **Redirect URIs** — the callback URL(s) Korey will redirect to after the user approves. Must use `https` in production; `http://localhost`, `http://127.0.0.1`, and `http://[::1]` are allowed for local development. **Port is ignored when matching loopback URIs** — per [RFC 8252 §7.3](https://www.rfc-editor.org/rfc/rfc8252#section-7.3), you can register `http://localhost/callback` and Korey will accept a redirect to any port on that host (e.g. `http://localhost:52413/callback`). This is intentional: native apps often listen on an ephemeral port that isn't known at registration time.
4. After creation, copy the **Client ID** and **Client secret**. The secret is shown once — store it securely (e.g. in a vault or environment variable).

  The client secret is equivalent to a password for your application. Never commit it to source control or expose it in client-side code. If it leaks, rotate it immediately from the OAuth Clients settings page.

### Public clients

If your app runs in an environment where a client secret cannot be kept confidential (e.g. a native app or a browser extension), you can use a **public client**. Public clients have no secret and use [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) (Proof Key for Code Exchange) instead to prove they initiated the authorization request.

Public clients are registered automatically via [Dynamic Client Registration](#dynamic-client-registration) — they cannot currently be created through the Settings UI.

With PKCE, before redirecting the user your app generates a random `code_verifier`, computes `code_challenge = BASE64URL(SHA256(code_verifier))`, and includes it in the authorization request. The `code_verifier` is then sent at the token exchange step so Korey can verify the two requests came from the same client.

### Authorization endpoint

Redirect the user to this URL to start the authorization flow. Korey will show the consent screen, and on approval redirect back to your `redirect_uri` with a short-lived `code` that you exchange for tokens. The `state` parameter is strongly recommended — generate a random value, store it in the user’s session, and verify it matches when Korey redirects back to prevent CSRF attacks.

```http
GET https://api.korey.ai/oauth/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=YOUR_REDIRECT_URI
  &scope=threads:read%20threads:write
  &state=RANDOM_NONCE
```

| Parameter | Required | Description |
|---|---|---|
| `response_type` | Yes | Must be `code` |
| `client_id` | Yes | Your registered client ID |
| `redirect_uri` | Yes | Must exactly match a registered redirect URI |
| `scope` | Yes | Space-separated list of requested scopes |
| `state` | Recommended | Opaque value you generate; returned unchanged to prevent CSRF |
| `code_challenge` | Public clients | `BASE64URL(SHA256(code_verifier))` |
| `code_challenge_method` | Public clients | Must be `S256` |

### Token endpoint

Both obtaining and refreshing tokens use the same endpoint with different `grant_type` values.

```http
POST https://api.korey.ai/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=CODE_FROM_REDIRECT
&redirect_uri=YOUR_REDIRECT_URI
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
```

```http
POST https://api.korey.ai/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=CODE_FROM_REDIRECT
&redirect_uri=YOUR_REDIRECT_URI
&client_id=YOUR_CLIENT_ID
&code_verifier=YOUR_CODE_VERIFIER
```

```http
POST https://api.korey.ai/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=YOUR_REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
```

Both grant types return the same token payload:

```json
{
  "access_token": "kt_oauth_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "kt_refresh_...",
  "scope": "threads:read"
}
```

### Revoking tokens

Revoke a token when a user disconnects your app, when a token is no longer needed, or if you suspect a token has been compromised. Revoking a token immediately invalidates it — any subsequent API calls using it will return `401 Unauthorized`. Both access tokens and refresh tokens can be revoked; revoking a refresh token also invalidates the associated access token.

```http
POST https://api.korey.ai/oauth/token/revoke
Content-Type: application/x-www-form-urlencoded

token=TOKEN_TO_REVOKE
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
```

The endpoint always returns `200 OK` regardless of whether the token existed or was already revoked — this prevents token enumeration.

## Client registration

Korey supports two ways to register an OAuth client programmatically, both using the same endpoint:

The request body is JSON and follows [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591):

```http
POST https://api.korey.ai/oauth/register
Content-Type: application/json

{
  "client_name": "My App",
  "redirect_uris": ["https://myapp.example.com/callback"],
  "scope": "threads:read threads:write",
  "token_endpoint_auth_method": "client_secret_basic",
  "client_uri": "https://myapp.example.com",
  "logo_uri": "https://myapp.example.com/logo.png"
}
```

| Field | Required | Description |
|---|---|---|
| `client_name` | Yes | Display name shown on the consent screen |
| `redirect_uris` | Yes | Array of allowed redirect URIs |
| `scope` | No | Space-separated scopes to request; defaults to all scopes |
| `token_endpoint_auth_method` | No | `client_secret_basic` (default) or `none` for public clients |
| `client_uri` | No | URL of the application's homepage |
| `logo_uri` | No | URL of the application's logo |
| `tos_uri` | No | URL of the terms of service |
| `policy_uri` | No | URL of the privacy policy |

On success Korey returns a `201` with the registered client including `client_id` and (for confidential clients) `client_secret`.

### Open registration

If no `Authorization` header is provided, the client is registered without being tied to any organization. It becomes associated with the first organization whose user approves the consent screen.

### Registration tokens

For tighter control, any Korey workspace member can generate a **one-time registration token** from **Settings → OAuth Clients → Registration token → Generate token**. The token is valid for **60 minutes** and single-use — it can only be consumed once.

Include it as a Bearer token on the registration request:

```http
POST https://api.korey.ai/oauth/register
Authorization: Bearer REGISTRATION_TOKEN
Content-Type: application/json

{ ... }
```

Using a registration token ties the new client to the generating user's organization immediately, rather than waiting for first consent.

### Dynamic Client Registration

Tools that support [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) can use the same endpoint with open registration — they discover it automatically via the OAuth metadata document at `/.well-known/oauth-authorization-server` and register themselves at first connection without any manual setup. MCP clients such as Cursor and Claude Code use this mechanism. See the [MCP page](/docs/developers/mcp) for details.