---
title: API Keys (unified key hub)
---

# API Keys (unified key hub)

## What it is

**API Keys** is the single Agent Tools sidebar row that holds everything key-related, behind two
tabs:

- **My API Keys** — your personal gateway key for your own automations (generate / copy /
  regenerate a `jls_sk_` key, see your pre-paid credit balance, the available APIs, and a
  copy-paste usage example).
- **Stored Keys** — a read-only inventory of every API key and secret Omniscio can see, grouped by
  where it lives (names + metadata only, never the values).

The two used to be separate sidebar rows ("My API Keys" + "Stored Keys") with near-identical key
icons stacked together, which read as a confusing duplicate. They merged into one **API Keys** row
on 2026-06-21. Nothing about how keys are stored, shown, or secured changed — this was a
navigation/layout cleanup. Each tab is the **exact** panel it always was, rendered verbatim.

## Where to find it

### Where it is

**Agent Tools → API Keys** in the sidebar — the last child row under the Agent Tools group,
after CLI Tools, Skills, and MCP Servers. Opening it shows a tab strip; the **My API Keys** tab is
active by default.

The **My API Keys** feature is _also_ still reachable as its own **Settings → My API Keys**
section (same component, unchanged) — only the duplicated sidebar rows were merged. There is no
Settings section for Stored Keys (it left Settings → Maintenance for the sidebar on 2026-06-01).

## How it behaves

### Tab 1 — My API Keys (personal gateway key)

Gives each Omniscio user a personal API key so their own scripts/automations can call a pool of AI/LLM
APIs through Omniscio's shared keys — without signing up for each provider. You use the pooled keys;
usage is metered and billed to your pre-paid credit balance at a flat surcharge. A **separate,
opt-in surface** — it does NOT change how Claude Code sessions, the active account, or any
provider you've connected behave.

The panel has four parts:

1. **Credits remaining** — your pre-paid balance (e.g. `$4.50`). At `$0.00` — or when the balance
   can't be verified — pooled-key creation is blocked **fail-closed** (you can't mint a key that
   would immediately be declined), with a **Refresh balance** control to retry; calls are likewise
   declined until credits are added (granted manually in v1).
2. **Your key** — one of three states: _not signed in_ → **Sign in with Google**; _no key yet_ →
   **Generate API key** — shown to **any signed-in user with confirmed credits** (the mint opened to
   all signed-in users on 2026-08-24 — previously Pro-only); without credits, an add-credits /
   use-your-own-OpenRouter-key recovery message, never a dead button that mints into a declined state
   (an _Upgrade to Pro_ prompt now appears only if the capability is ever re-gated); _have a key_ →
   the key's prefix (`jls_sk_abc…`) plus **Regenerate** (disabled without confirmed credits) /
   **Revoke**. The
   **full key is shown only once**, right after you generate it ("copy now — you won't see it
   again"); after that only the prefix is shown.
3. **Available APIs** — what you can call through the key. LLMs today: Claude, GPT, Groq, DeepSeek.
   Plus read-only **Twitter / X** (search + tweet/user lookups, billed per tweet returned). And the
   first two **non-LLM** APIs on the config-driven REST engine — **Firecrawl** (web scraping +
   crawling) and **Apify** (run scraper actors) — billed at the provider's reported usage with a
   per-job spend cap. Each non-default API is live once its gateway secret is deployed.
4. **How to use it** — the gateway base URL and a copy-paste `curl` example.

Point any OpenAI-compatible client at `<gateway base URL>/v1/<api>` with
`Authorization: Bearer <your key>`. The gateway swaps in the real provider key, forwards the
request, and bills your credits at cost + a flat surcharge. The key is long-lived (unlike your
hourly sign-in token), so an unattended automation (e.g. a 3am job) can use it. **Shown once,
never stored in the clear** (the gateway keeps only a one-way fingerprint); lost it → Regenerate.
**Scoped + safe** — a key spends only _your_ balance and is revocable instantly. One active key
per user in v1.

### Tab 2 — Stored Keys (key/secret inventory)

A searchable, read-only inventory of every key/secret Omniscio can see, grouped into four sources:
**Windows DPAPI vault** (`~/.claude/secrets/*.enc`, by filename), **Omniscio provider API keys** (xAI,
Groq, Deepgram, ElevenLabs, Speechify, Pika, OpenAI, Fish Audio, Picovoice, Pushbullet), **Claude accounts**, and
**automation credentials**. Each row shows the **name**, a **type label**, and a **last-updated**
date where one is recorded. A source with nothing in it is omitted.

Every row can be **removed** (confirmation-gated); "change the value" routes per source — vault
secrets get inline add/replace (re-encrypted with DPAPI) + a reversible move-to-trash delete with
**Undo**; provider keys clear-only; Claude accounts / automation credentials delete inline or
**Manage →** their Settings editor. Deleting an Omniscio-critical vault secret (e.g. `amc-cli`)
requires typing its name.

#### The safety rule (never-decrypt)

The panel can never expose a stored secret value — structural, not a convention:

- `KeyInventoryEntry` has **no** `value` / `secret` / `ciphertext` field — only `id`, `name`,
  `source`, `typeLabel`, `lastUpdated`.
- The vault reader lists `.enc` **filenames** only; deleting **moves** the encrypted file without
  opening it. Provider keys are reported present-or-not; clearing only blanks the field. Accounts /
  automation creds come from already-credential-stripped metadata.
- **Editing only ever sends a NEW value you type** (renderer → main); a stored value is never read
  back to the screen.

## For agents

### For AI agents

- Sidebar surface: the `api-keys` integration manifest
  ([integrations/api-keys.ts](../../src/shared/integrations/api-keys.ts), `parentGroupId:
'agent-tools'`) + the `API_KEYS_PROJECT_ID` (`__api_keys__`) virtual project. The panel
  [KeysView.tsx](../../src/renderer/src/features/keys/KeysView.tsx) is a thin tab shell
  (`panelOwnsLayout`, NON-spawnable) that renders the two EXISTING panels verbatim — neither
  feature's logic is touched by the merge. It sorts last among the Agent Tools children via the
  `amcDisplayOrderKey` override (`'API Keys' → 'Skills~'`) — see
  [agent-tools-contract.md](../../.claude/memory/contracts/agent-tools-contract.md).
- **My API Keys** tab → [MyApiKeysPanel.tsx](../../src/renderer/src/features/my-api-keys/MyApiKeysPanel.tsx)
  wraps the reused [ApiKeysSettings.tsx](../../src/renderer/src/features/settings/sections/api-keys/ApiKeysSettings.tsx)
  (ONE component, two mount points: this tab + Settings → My API Keys). Main: the
  `bundled-api:key-mint` / `key-list` / `key-revoke` / `balance` IPC handlers in
  [bundled-api-handlers.ts](../../src/main/ipc/bundled-api-handlers.ts) call the gateway with the
  user's Firebase ID token, Zod-validate the reply, and humanize errors. Gateway source now
  lives in this repo at `gateway/` (a self-contained Cloud Run service, deployed in
  the shares project) — `/v1/keys`, `/v1/balance`, `/v1/<provider>`.
  - **Gateway authorization (the allowlist) — Omniscio is the sole populator.** After the Firebase
    token verifies, every gateway request (even just _listing_ keys) must clear a Firestore
    `authorizedUsers/{email}` allowlist (`authorized: true`) in the shares project.
    `globalAuthProfile.getOrCreateProfile` ([admin-functions.ts](../../firebase/functions/src/global-auth/admin-functions.ts))
    upserts the caller on **every sign-in** (gated on `!disabled`), so a signed-in user is
    auto-authorized — **do not remove that write** or a valid login 401s ("could not be verified")
    on this screen even though the login is fine.
  - **Truthful 401s.** On a gateway 401/403, [bundled-api-handlers.ts](../../src/main/ipc/bundled-api-handlers.ts)
    re-verifies the token LOCALLY first: a still-valid token ⇒ server-side fault ⇒ "couldn't
    authorize your account … try again shortly", NOT "sign out and back in" (shown only when the
    token genuinely fails to verify). The status probe treats any non-5xx gateway response as
    reachable (the gateway has no `/health` route, so it 404s — still proof it is up).
- **Non-LLM APIs (Firecrawl / Apify) ride a config-driven REST engine.** Beyond the pooled LLMs + X,
  the gateway serves plain REST APIs from a per-provider CONFIG object — no new code per API.
  [rest-providers.ts](../../gateway/gateway/rest-providers.ts) declares the base URL, auth style, a
  default-deny `(method, path)` allow-list, and a per-route billing rule; ONE generic forwarder
  ([rest-provider.ts](../../gateway/gateway/rest-provider.ts)) serves them all. It handles sync calls
  AND async job lifecycles (start → poll → fetch) with usage pass-through billing: a hold reserved on
  start, settled once by job id on a terminal poll, every charge clamped to a per-job spend ceiling.
  Each provider is secret-gated (no `FIRECRAWL_API_KEY` / `APIFY_API_TOKEN` → the row 404s). Adding a
  non-LLM API is **one entry in the credit-vendor registry plus its secret** — the gateway's config and
  its registration are both derived from that entry, and a key field appears only if the user supplies
  the secret. READ
  [gateway-rest-provider-contract.md](../../.claude/memory/contracts/gateway-rest-provider-contract.md)
  before touching it or adding a provider.
- **Stored Keys** tab → [StoredKeysPanel.tsx](../../src/renderer/src/features/stored-keys/StoredKeysPanel.tsx).
  Read: `secrets:key-inventory-list` (returns `KeyInventoryEntry[]`). Write (all write-only — never
  return a stored value): `secrets:vault-set` / `secrets:vault-delete` (returns a `trashId` for
  undo) / `secrets:vault-restore`, and `secrets:provider-key-clear` (clear-only). Account /
  automation deletes reuse `account:remove` / `automation:credential-delete`. Invariant + enforcing
  tests: [key-inventory-contract.md](../../.claude/memory/contracts/key-inventory-contract.md).
- Existing installs that had the two old rows get the orphan `__stored_keys__` + `__my_api_keys__`
  virtual-project rows soft-deleted by a one-time migration (the startup seeder only ever ADDS
  rows, never prunes).

## Related

### See also

- [INDEX.md](INDEX.md)
- [agent-tools.md](agent-tools.md) — the parent sidebar group
- [model-vendors.md](model-vendors.md) — coding sessions on DeepSeek, GLM, Kimi, MiniMax, Meta, Qwen and OpenRouter can ALSO draw down this prepaid balance: the Omniscio credits row of each family's supply list (Settings → Accounts → Who pays & who serves) runs them on the company key through the gateway, no personal key needed
- [glm-provider.md](glm-provider.md) — GLM's credits row (Pro plan)
- [openrouter-provider.md](openrouter-provider.md) — OpenRouter's credits row — ungated (any signed-in user, no Pro tier), covering every model OpenRouter serves (~400), priced live through the gateway at a 10% markup
