Catalog providers store their key in $DSH_HOME/.credentials.yaml via Settings → Add provider. Custom providers do not store a key at all — apiKeyEnv names an environment variable that must be exported in the shell that launches dsh. Editing your shell profile does not affect a dsh process that is already running.
Credential problems in the harness split cleanly into two families, and they have opposite fixes. Work out which one you are in before changing anything.
Which kind of provider is failing?
Catalog provider — you picked DeepSeek, Anthropic, OpenAI or similar from Settings → Add
provider and pasted a key into the UI. The key is stored in $DSH_HOME/.credentials.yaml.
Custom provider — you supplied a provider ID, base URL, API protocol and model list yourself. Custom providers do not store the key. They reference an environment variable by name.
Most reports of "my key does not work" are the second kind, misdiagnosed as the first.
The apiKeyEnv trap
Here is the shape of a custom provider:
llm-pi-ai:
providers:
my-gateway:
apiKeyEnv: GATEWAY_API_KEY
api: openai-completions
baseURL: https://gateway.example/v1
models:
- id: vision-preview
input: [text, image]apiKeyEnv: GATEWAY_API_KEY means "read the key from the environment variable named
GATEWAY_API_KEY". It is not the key. Pasting your actual secret there does two bad things at
once: it fails to authenticate, and it writes a live credential into a settings file that is
designed to be shareable.
The fix is to export the variable in the shell you launch dsh from:
export GATEWAY_API_KEY="sk-..."
dsh --profile webWhy your export "did not work"
This is the second-most-common cause and it is pure process semantics.
A running process holds a snapshot of the environment it was started with. Adding a line to
~/.zshrc changes what future shells inherit. It does nothing to:
- a
dshprocess already running, - a terminal tab opened before you saved the file,
- a GUI-launched terminal that never sourced your login shell.
Verify in the exact shell you will launch from:
printenv GATEWAY_API_KEYEmpty output means the variable is not there, whatever your dotfiles say. Then restart dsh — not
just the browser tab.
Verify the endpoint independently
Before blaming the harness, confirm the credential and URL work at all:
curl -sS https://gateway.example/v1/models \
-H "Authorization: Bearer $GATEWAY_API_KEY" | head -20A 401 is a bad key. A 404 usually means the baseURL is wrong — a very common error is including or
omitting the /v1 suffix inconsistently with what api: openai-completions expects. A connection
error is network or DNS, not authentication.
This one command separates "the harness is misconfigured" from "the credential was never valid", and it is worth running before anything else.
Provider not found
If the harness reports an unknown provider rather than an auth failure, the reference and the definition have drifted apart. Two things to check.
The provider ID is permanent. You cannot rename it in place. If you tried, you now have a model reference pointing at an ID that no longer exists. Create a new provider with the ID you want and delete the old one.
The settings file may not be the one that booted. Print the composed configuration:
dsh --profile web --dump-configRemember the patch order — bundles, then the profile's cordis.patch.yml, then the home-level
$DSH_HOME/cordis.patch.yml, then any --patch overlay. A home-level patch overrides your profile
edit, and that is frequently the reason a provider you "definitely configured" is not there.
A checklist that resolves most cases
printenv <VAR>in the launching shell — is the variable actually set?curlthe endpoint with that key — is the credential valid at all?dsh --profile web --dump-config— did your provider survive composition?- Restart
dshfully after any environment change. - Confirm you did not paste a secret into
apiKeyEnv.
Frequently asked
Where are my API keys stored?
In $DSH_HOME/.credentials.yaml, kept separate from $DSH_HOME/settings.yaml so settings can be shared or version-controlled without leaking credentials.
Do I have to use a DeepSeek key?
No. The catalog includes providers such as Anthropic and OpenAI, and any OpenAI-compatible endpoint can be registered as a custom provider with its own base URL.
Can I rename a provider ID?
Not directly. The provider ID is permanent — create a new provider with the ID you want and delete the old one.