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

# OAuth (recommended)

> Connect Slack, Linear, GitHub, and Notion in one click — no copying tokens.

For Slack, Linear, GitHub, and Notion, the agent supports a one-click **OAuth flow** instead of copy-pasting API keys.

OAuth is the recommended path because it:

* Doesn't require users to leave the dashboard to generate keys.
* Records the **workspace name / team / repo owner** automatically (visible in `GET /v1/cofounder/integrations`).
* Uses provider-issued tokens that can be revoked from the provider's UI.
* Lets you offer the same SaaS experience your users expect from any modern integrations page.

Webhook and ASC-write don't need OAuth — they use a URL and the user's existing ASC connection.

***

## How the flow works

```
Frontend                        API                              Provider (Slack/Linear/…)
   │                             │                                       │
   │ 1. GET /oauth/slack/start  ─▶                                       │
   │                             │ create state row                       │
   │ ◀─ { authorizeUrl, state }  │                                       │
   │                             │                                       │
   │ 2. window.open(authorizeUrl) ───────────────────────────────────▶  │
   │                                                              user grants
   │                                                                     │
   │   3. provider redirects to /oauth/slack/callback?code=...&state=... │
   │                             ◀───────────────────────────────────────│
   │                             │ exchange code → token                  │
   │                             │ persist in cofounder_integrations      │
   │                             │ redirect to return_to?cofounder_oauth=success
   │ ◀──────────────────────────│                                       │
   │ show "Connected!" banner    │                                       │
```

Two endpoints, both under the cofounder router:

| Method & path                                | Auth                       | Purpose                                                        |
| -------------------------------------------- | -------------------------- | -------------------------------------------------------------- |
| `GET /v1/cofounder/oauth/:provider/start`    | API key / web JWT          | Returns `{ authorizeUrl, state }` for the user to open         |
| `GET /v1/cofounder/oauth/:provider/callback` | none (verified by `state`) | Exchanges code, persists credentials, redirects to `return_to` |

`:provider` ∈ `slack | linear | github | notion`.

***

## Frontend usage

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function connectSlack() {
  const res = await fetch(
    "https://api.appeeky.com/v1/cofounder/oauth/slack/start" +
      `?return_to=${encodeURIComponent(window.location.href)}`,
    { headers: { "X-API-Key": APPEEKY_KEY } }
  );
  const { data } = await res.json();
  // Redirect (or open in a popup) — provider will redirect back to your page
  window.location.href = data.authorizeUrl;
}
```

After the round-trip your page is reloaded with `?cofounder_oauth=success&provider=slack&account=Acme%20Workspace` in the query string. Detect this in your page-load code, refresh the integrations list, and show a toast.

Failure cases also redirect back, with `?cofounder_oauth=denied` (user clicked Cancel) or `?cofounder_oauth=error&error=...` (token exchange failed). Always inspect both shapes.

***

## Server configuration

For each provider you want to support, create an OAuth client in the provider's developer console. Set the **Redirect URI** to:

```
https://api.appeeky.com/v1/cofounder/oauth/<provider>/callback
```

Then set the corresponding env vars:

| Provider | Env vars                                                                                                                                        |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Slack    | `SLACK_CLIENT_ID`, `SLACK_CLIENT_SECRET`, optional `SLACK_OAUTH_BOT_SCOPES` (default: `chat:write,chat:write.public,channels:read,groups:read`) |
| Linear   | `LINEAR_CLIENT_ID`, `LINEAR_CLIENT_SECRET`, optional `LINEAR_OAUTH_SCOPES` (default: `read,write,issues:create`)                                |
| GitHub   | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, optional `GITHUB_OAUTH_SCOPES` (default: `repo`)                                                    |
| Notion   | `NOTION_CLIENT_ID`, `NOTION_CLIENT_SECRET`                                                                                                      |

Cross-cutting:

| Env var                               | Purpose                                               | Default                                                            |
| ------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------ |
| `COFOUNDER_OAUTH_BASE_URL`            | Public URL of THIS API (callback host)                | `https://api.appeeky.com`                                          |
| `COFOUNDER_OAUTH_DEFAULT_RETURN_TO`   | Fallback return URL                                   | `https://app.appeeky.com/integrations`                             |
| `COFOUNDER_OAUTH_RETURN_TO_ALLOWLIST` | Comma-separated host prefix allowlist for `return_to` | `https://app.appeeky.com,https://api.appeeky.com,http://localhost` |

`return_to` MUST start with one of the allowlisted prefixes — otherwise it's rejected (open-redirect guard).

***

## What gets stored

After a successful OAuth round-trip, a row is upserted in `cofounder_integrations`:

```
provider             slack
auth_method          oauth
oauth_account_label  "Acme Workspace"        ← shown in the dashboard
oauth_external_id    "T012ABC"               ← team/workspace/repo id from provider
oauth_scope          "chat:write,channels:read,..."
oauth_expires_at     null                     ← Slack/Linear/GitHub/Notion all issue long-lived tokens
encrypted_credentials  { accessToken, botToken, teamId, ... }
```

`encrypted_credentials` is **never** returned by `GET /v1/cofounder/integrations`. The dashboard only sees the metadata above plus `enabled` and `last_used_at`.

***

## Re-connect / Switch workspace

Re-running `/oauth/:provider/start` and completing the flow upserts the same `(owner_email, provider)` row — pointing the integration at the new workspace. The previous token is overwritten and effectively forgotten by the agent (it remains valid on the provider's side until the user revokes it from there).

## Disconnect

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X DELETE https://api.appeeky.com/v1/cofounder/integrations/slack \
  -H "X-API-Key: $APPEEKY_KEY"
```

This removes the credentials from our database. **It does not** revoke the token on Slack — go to your Slack workspace's *Apps* page if you want to revoke it there too.

***

## State row lifecycle

Each `/start` creates a `cofounder_oauth_states` row that:

* Holds the random `state`, the user's email, the provider, and (if applicable) the PKCE `code_verifier`.
* Expires after **10 minutes**.
* Is deleted as soon as the callback consumes it (single-use).

Expired rows are purged opportunistically on every `/start` call.

***

## Manual API-key path (still supported)

If you'd rather skip OAuth — for example, scripting your own integration — you can `PUT /v1/cofounder/integrations/:provider` with the credential JSON directly. See each provider's page for the schema. OAuth-managed and manually-managed rows coexist; the latest one wins.
