Key Management
API keys are never written into configuration files. There are exactly two sources:
| Source | How it works | When to use |
|---|---|---|
| Environment variable | Read at runtime from an env var. On by default (auto_detect_env: true). |
Local dev, CI/CD — just export the variable |
| System keyring | Stored encrypted in the OS credential store (macOS Keychain / Windows Credential Manager / Linux Secret Service) via zlogic key add. |
Persistent keys on your machine |
Resolution priority per provider: environment variable first, then the keyring. Keys that cannot be resolved simply make that provider unavailable — nothing is ever sent unauthenticated.
Environment variables (default)
Just export the variable — no key add step needed:
export DEEPSEEK_API_KEY=sk-...
- Built-in providers recognize their official variable names (
OPENAI_API_KEY,ANTHROPIC_API_KEY,DEEPSEEK_API_KEY,GEMINI_API_KEY,GOOGLE_API_KEY,GOOGLE_GENERATIVE_AI_API_KEY,DASHSCOPE_API_KEY,ZHIPUAI_API_KEY,GLM_API_KEY,OPENROUTER_API_KEY). - The generic pattern is
<PROVIDER>_API_KEY(provider id uppercased). - To turn environment keys off entirely, set
auto_detect_env: falseinmodels.yaml— after that only the keyring is consulted.
System keyring (persistent)
# Add a key for a provider (prompts for the secret)
zlogic key add openai
# List which providers have keyring entries (masked, never the plaintext)
zlogic key list
# Verify a credential actually works against the provider
zlogic key verify openai
zlogic key verify openai gpt-4o
# Remove a keyring entry
zlogic key remove openai --yes
Keyring entries are referenced by provider id (keyring:openai). The
desktop Settings UI adds keys the same way — choose the provider and pick a
credential source.
Keys for web search (the web_search tool)
The search tool needs no key by default: Exa and Parallel both have a free tier, with a low quota. When that runs out, store a key for the backend — same two sources as a provider, with a prefixed entry name:
| Backend | Get a key | Environment variable | Keyring entry |
|---|---|---|---|
exa |
https://dashboard.exa.ai/api-keys | EXA_API_KEY (or ZLOGIC_EXA_API_KEY) |
web_search:exa |
parallel |
https://platform.parallel.ai | PARALLEL_API_KEY (or ZLOGIC_PARALLEL_API_KEY) |
web_search:parallel |
zlogic key set exa # prompts, writes web_search:exa
zlogic key set parallel
In the desktop app, fill it in place under Settings → Tools → Web search;
the link at the right of each row is the page from the table above. Provider
cards (Settings → Models) carry the same link, taken from the provider's
guide_url. The key is read on every search, so the next one uses it — no
restart. The prefix matters: exa / parallel are retrieval services rather
than model providers, and they cannot share a keyring entry with a same-named
provider.
env.yaml — keys in a single file
If you would rather not export every time and do not want to use the keyring,
drop an env.yaml into the config root (~/.config/zlogic/env.yaml). The
system loads it as environment variables at startup (a real environment
variable always wins; env.yaml only fills in what is missing):
# ~/.config/zlogic/env.yaml
DEEPSEEK_API_KEY: sk-...
OPENAI_API_KEY: sk-...
- Plain
KEY: valuepairs (uppercase key names; the values are plaintext secrets — keep the file private). - Values never override an existing process environment variable of the same name.
- Exactly equivalent to
export: theauto_detect_envswitch applies the same way. - Do not commit it; add
env.yamlto.gitignore.
Security recommendations
- Personal machines:
zlogic key add <provider>once — the OS keyring decrypts transparently afterwards. - CI/CD and shared environments: use environment variables injected by your secrets manager.
- Never write API keys as plaintext in any configuration file or repository.