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

# Connect and manage apps

> Connect apps, API keys, and OAuth accounts from the web app or CLI.

You can connect apps and manage credentials from either the web app or the CLI. Use the web app when you want a guided flow. Use the CLI when you're scripting setup or setting project-scoped credentials during deploy work.

## Connect from the web app

Open **Apps** in the web app to see connected apps and add new ones.

The Apps page is organized around credential instances: each connected app row represents a credential at an organization, project, or user scope. From there you can:

* Connect an OAuth app (click "Connect an app" to see all available apps, and select an app to create a new connection).
* Store an API key for an app.
* Choose where the credential is available: organization, user, or selected projects.
* Rename a credential, change its scope, duplicate it into other scopes, set it as default, rotate manual secrets, or revoke it.

On a credential's detail page, open the actions menu for **Change scope** (move the same record to another scope you can access) or **Duplicate** (create copies with a new name and one or more target scopes).

OAuth connections open the provider's authorization page. API key apps show a secret input and store the value in Keystroke's credential vault.

<Note>
  Building over the [Keystroke MCP server](/docs/build-with-ai/mcp-for-agents)? The agent's `connect_app` tool returns a link to this same web flow — a hosted workspace can't open a browser, so you authorize the integration here.
</Note>

## Connect from the CLI

Run `keystroke credentials list` before connecting an app. The needed account may already be available at organization, project, or user scope.

The CLI has two main paths:

| Command                              | Use it for                                                                                     |
| ------------------------------------ | ---------------------------------------------------------------------------------------------- |
| `keystroke connect <slug>`           | Any app — opens the web connect flow (OAuth approval or an API-key form, depending on the app) |
| `keystroke credentials create <key>` | Headless/scripted API keys when you already have the secret (no browser)                       |

List connectable apps:

```bash theme={null}
keystroke apps list
```

Connect an app. `connect` works for every connection type; the web flow handles OAuth or API-key entry and lets you choose the scope — there are no scope flags on `connect`:

```bash theme={null}
keystroke connect github
keystroke connect exa
```

In headless or hosted environments — coding agents, the in-app [platform agent](/docs/build-with-ai/platform-agent), CI — pass `--print-url` and share the printed link with the user instead of opening a browser:

```bash theme={null}
keystroke connect github --print-url
```

For Slack, `keystroke connect slack` opens the same chooser as **Connect Slack** in the web app: the Keystroke Slack App (catalog slug `slackbot`), a Custom Slack App, or a Personal Slack Account (catalog slug `slack`). Open one dialog directly with `--kind keystroke|personal|custom`. For External Channels, connect the Keystroke Slack App (`keystroke connect slack --kind keystroke --wait` / `--print-url`), then `keystroke channels setup <agent>` — not a personal account. Custom @handles use `channels setup <agent> --custom` (web wizard). See [external channels](/docs/learn/agents/external-channels).

Store an API key without opening a browser:

```bash theme={null}
keystroke credentials create exa --set apiKey=@env:EXA_API_KEY --scope org
```

`--set` accepts either an inline value (`apiKey=sk-...`) or an environment reference (`apiKey=@env:EXA_API_KEY`). Environment references load from the shell and the project's `.env` file when available — so they only apply on a local machine or CI. In hosted sessions there is no `.env`; use `keystroke connect <slug> --print-url` and let the user enter the key in the web flow.

## Credential scopes

Manual credentials created with `keystroke credentials create` take one or more scopes. (`keystroke connect` picks scope in the web flow instead — the flags below apply to `credentials create`.)

| Scope flag                              | Meaning                                        |
| --------------------------------------- | ---------------------------------------------- |
| `--scope org`                           | Create an organization credential              |
| `--scope user`                          | Create a credential for the authenticated user |
| `--scope project --project-slug <slug>` | Create a credential for one project            |

If you omit `--scope`, the CLI creates an organization credential.

You can repeat `--scope` and `--project-slug` to fan out one credential to several targets:

```bash theme={null}
keystroke credentials create exa \
  --set apiKey=@env:EXA_API_KEY \
  --scope org \
  --scope project \
  --project-slug production \
  --project-slug staging
```

Project scope requires `--project-slug` because the credential must be written to a specific platform project.

## Credentials and deploy

```bash theme={null}
keystroke credentials create exa --set apiKey=@env:EXA_API_KEY --scope project --project-slug production
```

Deploy does not upload `.env` or local secrets. For deployed runs, set credentials with the web app or CLI. Workers materialize the selected credential from the platform when an action runs.

## Manage credentials

Use the Apps page for day-to-day management, or use CLI commands when scripting.

```bash theme={null}
keystroke credentials list
keystroke credentials get <credential-id>
keystroke credentials update <credential-id> --label "Production Exa" --default
keystroke credentials rotate <credential-id> --set apiKey=@env:EXA_API_KEY
keystroke credentials delete <credential-id>
```

A few details:

* When several credentials could satisfy an action, choose the intended account rather than creating another connection.
* `rotate` is for cloud credentials and replaces a manual secret value.
* `update --default` marks one credential as the default within its app and scope.
* If multiple credentials exist for the same app and scope and none is the single default, a run needs an explicit selection — bind one with an assignment (below).

### Reconnect a credential

When an OAuth credential expires or needs to be reauthorized (for example, when permissions change), use `credentials reconnect` to refresh the connection without creating a new credential instance:

```bash theme={null}
keystroke credentials reconnect <credential-id>
keystroke credentials reconnect <credential-id> --print-url  # for headless/hosted environments
```

This opens the same OAuth flow as `connect`, but updates the existing credential instance. Assignments, scope, and label remain unchanged — only the OAuth tokens are refreshed. In headless or hosted sessions (coding agents, CI), pass `--print-url` and share the printed link with the user.

### Change a credential's scope

Move an existing credential **in place** (same id) to a scope you can access — for example promote a user credential to a project, or a project credential to the organization. You must have access to the destination (project membership for project scope; user destination is always your own user).

Use a **single** `--scope` value (unlike `create`, which can fan out):

```bash theme={null}
# User → project
keystroke credentials update <credential-id> --scope project --project-slug production

# Project → organization
keystroke credentials update <credential-id> --scope org

# Organization → user (your user)
keystroke credentials update <credential-id> --scope user
```

Assignments that point at this credential instance keep working after a scope swap (they reference the instance id). OAuth credentials keep the same connection; only the scope changes.

### Duplicate a credential

Create one or more copies of an existing credential with a new label and destination scopes. The default name is `"{label} (copy)"` when you omit `--name` / `--label`. Copies are never marked default.

```bash theme={null}
# Copy into org + a project (API key secrets are copied; OAuth shares the connection)
keystroke credentials duplicate <credential-id> \
  --name "Staging Exa" \
  --scope org \
  --scope project \
  --project-slug staging
```

`--scope` / `--project-slug` use the same fan-out rules as `credentials create`. Prefer duplicate when you need the same secret or OAuth account at another scope without reconnecting; prefer change-scope when you want a single credential to move.

## Bind a credential to a step, tool, or poll action

A default resolves the common case, but sometimes one workflow step, agent tool, or poll action must use a *specific* instance — a particular project's Linear connection, a dedicated service account, one of several org credentials, or a **user-scoped** credential. An **assignment** pins an exact credential instance to a single consumer. It is the explicit selection at the top of the [resolution order](/docs/learn/credentials/use-credentials#resolution-order), so it wins over scope defaults.

User-scoped credentials are not auto-picked from run identity. If an action is pinned with `.scope("user")`, assign that person's user credential to the step, tool, or poll consumer (CLI or API). That association is what makes the run succeed.

First list the bindable consumers for a workflow, agent, or poll, then assign:

```bash theme={null}
# Workflow consumers are step correlation ids from recent runs (step:<slug>#<n>)
keystroke credentials consumers list --workflow sync
keystroke credentials assignments assign --workflow sync --credential org/work --consumer step:fetch-gmail#0

# Agent consumers are tool slugs
keystroke credentials consumers list --agent support
keystroke credentials assignments assign --agent support --credential vault-prod --consumer vault-lookup

# Poll consumers are the slugs of actions called inside the poll's run()
keystroke credentials consumers list --poll new-inbox
keystroke credentials assignments assign --poll new-inbox --credential gmail/support --consumer fetch-inbox
```

* Pass exactly one of `--workflow <slug>`, `--agent <slug>`, or `--poll <slug>`.
* `--credential` takes a credential instance slug: `work`, `org/work`, or `<app>/<slug>` (for example `linear/work`).
* Omit `--consumer` (or pass `*`) to bind every consumer on the target instead of one.

Inspect and remove assignments:

```bash theme={null}
keystroke credentials assignments list --workflow sync
keystroke credentials assignments unassign <assignment-id>
```

<Note>
  Workflow consumer ids come from `step_completed` events, so they appear only after the workflow has run at least once. Agent consumers include known tool slugs plus any from existing assignments. Poll consumers only come from existing assignments — name the action slug explicitly (or use the wildcard) on the first assign.
</Note>

## External channel apps

External channel apps connect agents to chat tools. Slack is the primary one today. Prefer the setup happy path:

```bash theme={null}
keystroke connect slack --kind keystroke --wait
keystroke channels setup support --channel support
```

Custom bot identity (dedicated @handle): `keystroke channels setup support --custom` opens the agent wizard. See [external channels](/docs/learn/agents/external-channels).

## Next steps

<CardGroup cols={2}>
  <Card title="Built-in integrations" href="/docs/learn/credentials/built-in-integrations">
    Understand the app catalog and managed integration paths.
  </Card>

  <Card title="Using credentials in code" href="/docs/learn/credentials/use-credentials">
    Control how actions resolve credentials at runtime.
  </Card>

  <Card title="External channels" href="/docs/learn/agents/external-channels">
    Connect Slack channels to agents.
  </Card>

  <Card title="CLI reference" href="/docs/cli">
    See every credential, connect, channel, and API key command.
  </Card>
</CardGroup>
