# Configure API keys and providers

> Store Anthropic, OpenAI, Google, Foundry, Cursor, DeepSeek, and custom proxy models; provider CLI detection; secure key interception; and optional PostHog analytics keys.

- Repository: Parcha-ai/build
- GitHub: https://github.com/Parcha-ai/build
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b/llms-full.txt

## Source Files

- `src/main/services/settings.service.ts`
- `src/main/services/secure-keys.service.ts`
- `src/main/ipc/secure-keys.ipc.ts`
- `src/renderer/components/settings/SettingsDialog.tsx`
- `src/shared/types/index.ts`
- `SECURITY.md`

---

---
title: "Configure API keys and providers"
description: "Store Anthropic, OpenAI, Google, Foundry, Cursor, DeepSeek, and custom proxy models; provider CLI detection; secure key interception; and optional PostHog analytics keys."
---

Build stores credentials locally in `electron-store` (`claudette-settings` on disk under the app userData path), exposes them through Settings → **API Keys** and dedicated IPC handlers on `window.electronAPI`, and never sends stored keys to Build servers—only to the provider APIs you configure. Harness CLIs can authenticate via OAuth/login on disk instead of an in-app key; onboarding and `auth:check-providers` report which paths are ready.

## Storage layout

All persistent secrets use the same store file name: `claudette-settings`. Keys are split between a nested `settings` object (`AppSettings`) and a few top-level keys for dedicated getters.

| Storage key / field | Consumer | How it is written |
| --- | --- | --- |
| `anthropicApiKey` (top-level) | Claude Agent SDK (`ClaudeService.getApiKey`) | `settings.setApiKey` → `SETTINGS_SET_API_KEY` |
| `settings` (`AppSettings`) | Model list, Foundry, Cursor, DeepSeek, Gemini CLI, custom proxies, optional PostHog | `settings.set` partial updates |
| `googleApiKey` (top-level) | Stagehand / browser AI (`settings.getGoogleApiKey`) | `settings.setGoogleApiKey` |
| `openaiApiKey` (top-level) | Whisper transcription (`AudioService`) | `audio.setOpenAiKey` |
| `elevenLabsApiKey` (top-level) | ElevenLabs TTS / voice (`AudioService`, `elevenlabs-voice.service`) | `audio.setElevenLabsKey` |
| `githubToken` (top-level) | GitHub integration (`SettingsService`) | `SettingsService.setGitHubToken` (typed on `AppSettings`, not shown in API Keys tab) |
| `audioSettings` | Voice mode toggles, agent ID | `audio.setSettings` |

<Note>
`SECURITY.md` states keys are only sent to providers and that no telemetry is collected. In code, **optional** PostHog export runs only when `posthogApiKey` (or build-time env vars) is set; without a key, `AnalyticsService` skips network capture.
</Note>

### `AppSettings` credential fields

Defined in `AppSettings` (`src/shared/types/index.ts`):

<ParamField body="anthropicApiKey" type="string">
Legacy duplicate on the type; the live Anthropic key is stored at top-level `anthropicApiKey` via `SettingsService`.
</ParamField>

<ParamField body="foundryEnabled" type="boolean">
Enables Azure Anthropic Foundry routing for Claude Code (`CLAUDE_CODE_USE_FOUNDRY=1`).
</ParamField>

<ParamField body="foundryBaseUrl" type="string">
Maps to `ANTHROPIC_FOUNDRY_BASE_URL` when Foundry is enabled.
</ParamField>

<ParamField body="foundryApiKey" type="string">
Maps to `ANTHROPIC_FOUNDRY_API_KEY`.
</ParamField>

<ParamField body="foundryDefaultSonnetModel" type="string">
Optional Foundry model IDs for Sonnet/Haiku/Opus tiers in the picker.
</ParamField>

<ParamField body="foundryDefaultHaikuModel" type="string">
</ParamField>

<ParamField body="foundryDefaultOpusModel" type="string">
</ParamField>

<ParamField body="cursorApiKey" type="string">
Cursor harness / SDK model listing (local sessions).
</ParamField>

<ParamField body="deepseekApiKey" type="string">
OpenCode harness (`DEEPSEEK_API_KEY` env).
</ParamField>

<ParamField body="geminiApiKey" type="string">
Gemini CLI harness (`GEMINI_API_KEY` / `GOOGLE_API_KEY`).
</ParamField>

<ParamField body="customModels" type="CustomModelConfig[]">
Anthropic-compatible proxy endpoints (`id`, `name`, `modelId`, `baseUrl`, `apiKey`, optional `description`).
</ParamField>

<ParamField body="posthogApiKey" type="string">
Optional project API key for analytics capture (no Settings UI field today).
</ParamField>

<ParamField body="posthogHost" type="string">
PostHog host; defaults to `https://us.i.posthog.com` when empty.
</ParamField>

```typescript
// CustomModelConfig shape
{
  id: string;       // picker id, e.g. "kimi-k26"
  name: string;     // display name
  modelId: string;  // API model id
  baseUrl: string;  // Anthropic-compatible base URL
  apiKey: string;
  description?: string;
}
```

## Settings UI workflow

Open **Settings** (gear) → **API Keys** tab (`SettingsDialog`, tab id `apiKeys`).

<Steps>
<Step title="Anthropic">
Enter `sk-ant-...` or skip and use `claude login` in a terminal (OAuth via Claude Code credentials).
</Step>
<Step title="Foundry (optional)">
Toggle **Anthropic Foundry**, set base URL, API key, and optional Sonnet/Haiku/Opus model name overrides.
</Step>
<Step title="OpenAI / ElevenLabs">
OpenAI key is labeled **Speech-to-Text** (Whisper). ElevenLabs key and agent ID power voice mode.
</Step>
<Step title="Cursor / DeepSeek / Gemini">
Cursor API key for the Cursor harness; DeepSeek for OpenCode; Gemini API key for the `gemini` CLI harness.
</Step>
<Step title="Google (browser AI)">
Separate **Google/Gemini API Key (Browser AI)** for Stagehand (`AIza...`), stored via `setGoogleApiKey`.
</Step>
<Step title="Custom models">
Use **+ Add Model** for Anthropic-compatible proxies (e.g. Kimi). Saves into `settings.customModels` and reloads the model picker.
</Step>
</Steps>

Text fields debounce 500ms; toggles and explicit saves call `autoSaveAppSettings` or `autoSaveApiKey` immediately. Model-affecting updates trigger `loadAvailableModels()` in the session store.

## IPC and preload surface

Renderer calls go through `preload.ts` → `ipcMain` handlers.

| Channel | Handler | Purpose |
| --- | --- | --- |
| `settings:get` | `settings.ipc` | Read full `AppSettings` |
| `settings:set` | `settings.ipc` | Merge partial `AppSettings` |
| `settings:get-api-key` / `settings:set-api-key` | `settings.ipc` | Anthropic top-level key |
| `settings:get-google-api-key` / `settings:set-google-api-key` | `settings.ipc` | Browser AI key |
| `audio:get-openai-key` / `audio:set-openai-key` | audio IPC | Whisper key |
| `audio:get-elevenlabs-key` / `audio:set-elevenlabs-key` | audio IPC | ElevenLabs key |
| `auth:check-providers` | `auth.ipc` | CLI + credential detection |
| `secure-keys:intercept` | `secure-keys.ipc` | Detect/replace keys in chat text |
| `secure-keys:get` | `secure-keys.ipc` | Resolve placeholder by id (agent use) |
| `secure-keys:list` | `secure-keys.ipc` | Metadata only per session |
| `secure-keys:clear-session` | `secure-keys.ipc` | Wipe session memory keys |

Preload grouping:

```typescript
window.electronAPI.settings.get / .set / .getApiKey / .setApiKey / .getGoogleApiKey / .setGoogleApiKey
window.electronAPI.audio.getOpenAiKey / .setOpenAiKey / .getElevenLabsKey / .setElevenLabsKey
window.electronAPI.auth.checkProviders()
window.electronAPI.secureKeys.interceptAndReplace / .getKey / .listKeys / .clearSession
```

## Provider CLI detection

`AUTH_CHECK_PROVIDERS` runs five checks in parallel (`auth.ipc.ts`) and returns `{ claude, codex, cursor, gemini, opencode }` with `ProviderStatus`: `installed`, `loggedIn`, `method` (`cli` | `apiKey` | `chatgpt`), `detail`, `path`, `version`, plus install/login hints.

```mermaid
flowchart TB
  subgraph UI["Renderer"]
    Onboarding["ApiKeyOnboarding"]
    Settings["SettingsDialog"]
  end
  subgraph Main["Main process auth.ipc"]
    Check["AUTH_CHECK_PROVIDERS"]
    Claude["checkClaudeCli"]
    Codex["checkCodexCli"]
    Cursor["checkCursorCli"]
    Gemini["checkGeminiCli"]
    OpenCode["checkOpenCodeCli"]
  end
  subgraph Disk["Local auth artifacts"]
    KC["macOS Keychain / ~/.claude/.credentials.json"]
    CodexAuth["~/.codex/auth.json"]
    CursorCLI["cursor-agent status"]
    Store["claudette-settings"]
  end
  Onboarding --> Check
  Settings --> Store
  Check --> Claude --> KC
  Check --> Codex --> CodexAuth
  Check --> Cursor --> CursorCLI
  Check --> Cursor --> Store
  Check --> Gemini --> Store
  Check --> OpenCode --> Store
```

| Provider | CLI binaries searched | Logged-in signal |
| --- | --- | --- |
| Claude | `claude` | macOS keychain `Claude Code-credentials` or `~/.claude/.credentials.json` |
| Codex | `codex` (+ bundled platform binary) | `~/.codex/auth.json` tokens or `OPENAI_API_KEY` |
| Cursor | `cursor-agent`, `agent` (common paths) | `cursor-agent status` shows logged in, or `cursorApiKey` / `CURSOR_API_KEY` |
| Gemini | `gemini` | CLI installed **and** `geminiApiKey`, `googleApiKey`, or `GEMINI_API_KEY` / `GOOGLE_API_KEY` |
| OpenCode | `opencode` or `npx` | Runner available **and** `deepseekApiKey` or `DEEPSEEK_API_KEY` |

Onboarding (`ApiKeyOnboarding.tsx`) animates scan phases while calling `checkProviders`, then polls until all five report `loggedIn` or the user continues with an Anthropic API key.

<Warning>
Provider detection uses `realUserHome()` on macOS so demo `HOME` overrides do not hide real keychain/credential state.
</Warning>

## How keys reach harnesses

```text
claudette-settings
├── anthropicApiKey ──────────► Claude Agent SDK (ANTHROPIC_API_KEY)
├── settings.foundry* ────────► getFoundryEnvVars() → Claude Code Foundry env
├── settings.customModels ────► getCustomModelEnvVars() → ANTHROPIC_BASE_URL / _API_KEY
├── settings.cursorApiKey ────► Cursor SDK + cursor harness
├── settings.deepseekApiKey ──► opencode.service → DEEPSEEK_API_KEY
├── settings.geminiApiKey ────► gemini.service (+ fallback googleApiKey)
├── googleApiKey ─────────────► stagehand.service (browser automation)
├── openaiApiKey ─────────────► audio.service Whisper
└── elevenLabsApiKey ─────────► voice / TTS services
```

- **Foundry**: When `foundryEnabled`, `ClaudeService.getAvailableModels()` lists configured Foundry model names instead of default Anthropic IDs.
- **Custom proxy**: Picker ids use prefix `custom:{id}`; env overrides `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and tier model vars.
- **Codex**: Prefers `~/.codex/auth.json`; Build-stored `openaiApiKey` is also read for routing cost checks.
- **Audio fallbacks**: `AudioService` may use `EMBEDDED_KEYS` when the user has not set ElevenLabs/OpenAI keys (development convenience only).

## Secure key interception

`SecureKeysService` keeps detected secrets **in memory only** (singleton `Map`, cleared per session via `clearSessionKeys`). They are not written to `electron-store` or session transcripts as plaintext in the UI path.

**Detection order:**

1. `ENV_VAR=value` lines where the name matches secret patterns (`API_KEY`, `TOKEN`, `SECRET`, AWS keys, etc.) and value is not a placeholder.
2. Provider-specific regexes (`sk-ant-`, `sk-`, `ghp_`, `AIza`, Stripe, Slack, JWT, etc.).
3. Generic high-entropy tokens (Shannon entropy ≥ 3.5, ≥ 3 character classes).

**Send path** (`session.store.ts`):

1. Before enqueue/send, `secureKeys.interceptAndReplace(sessionId, message)` replaces raw values with `[SECURE_KEY:key_<hex>]`.
2. UI stores `keysDetected` metadata (descriptions only).
3. On session end, `secureKeys.clearSession(sessionId)`.

**Agent path** (`claude.service.ts`):

1. Before the model sees the user turn, placeholders in `streamMessage` are resolved back to real values via `secureKeysService.getKey`.
2. Env assignments detected in chat can be exported to a session-scoped env file (`prepareSecureEnvContext`) for local/SSH runs, mode `0600`.

<Info>
Logs record key **ids** and types, never values. `SECURE_KEYS_GET` returns the value to the main process for resolution—keep agent tooling on the IPC bridge, not renderer storage.
</Info>

```mermaid
sequenceDiagram
  participant R as Renderer session.store
  participant IPC as secure-keys IPC
  participant SK as SecureKeysService
  participant CS as ClaudeService
  participant M as Model / CLI
  R->>IPC: intercept(sessionId, text)
  IPC->>SK: interceptAndReplaceKeys
  SK-->>R: modifiedText + keysDetected
  R->>R: Persist chat with placeholders
  R->>CS: streamMessage(placeholder text)
  CS->>SK: getKey(keyId) per placeholder
  SK-->>CS: resolved secrets
  CS->>M: prompt with real values
```

Codex IPC applies the same intercept step on prompts before execution.

## Optional PostHog analytics

Routing and product analytics use `AnalyticsService` (`claudette-analytics` store for events; `posthog.distinctId` for anonymous id).

**Configuration precedence** (`getPostHogConfig`):

1. `settings.posthogApiKey` / `settings.posthogHost` in `claudette-settings`
2. Env: `BUILD_POSTHOG_API_KEY`, `POSTHOG_PROJECT_API_KEY`, `POSTHOG_API_KEY`, `POSTHOG_HOST`

If `apiKey` is empty, `capturePostHog` returns without network I/O. Payloads sanitize properties (drop raw `prompt`, `sessionName`, `error`; trim `routingDecision`). Capture sets `$process_person_profile: false`, disables geo IP, and uses `fetch` to `{host}/capture/`.

There is a separate `posthog.service.ts` wired to **build-time** `POSTHOG_API_KEY` env for `posthog-node`; user-facing optional keys in settings follow the `AnalyticsService` path above. No **API Keys** UI fields exist for PostHog today—set via `settings.set({ posthogApiKey, posthogHost })` or direct store edit.

## Verification

| Check | Expected signal |
| --- | --- |
| Anthropic key saved | `settings.getApiKey()` non-empty; Claude sessions start without “missing API key” |
| Provider scan | Onboarding shows green/ready for CLI you installed; `auth.checkProviders()` matches terminal auth |
| Custom model | Model picker shows `custom:{id}`; agent env includes your `baseUrl` |
| Secure intercept | Console: `Intercepted N API key(s)`; chat shows `[SECURE_KEY:...]` not raw `sk-` |
| PostHog (optional) | With key set, network POST to your PostHog host; without key, no capture attempts |

Report security issues per `SECURITY.md` to **security@parcha.ai** (do not file public issues for vulnerabilities).

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
First project, API key, harness pick, and first agent message.
</Card>
<Card title="Harnesses and models" href="/harnesses-and-models">
Harness ids, model strings, and permission modes per harness.
</Card>
<Card title="Settings reference" href="/settings-reference">
Full `AppSettings` and `claudette-settings` keys.
</Card>
<Card title="IPC and preload bridge" href="/ipc-bridge">
How `electronAPI` maps to `ipcMain` handlers.
</Card>
<Card title="Voice and audio" href="/voice-and-audio">
ElevenLabs, OpenAI realtime, and microphone setup.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
PATH, CLI detection, and credential recovery.
</Card>
</CardGroup>
