ErrLookupBackground articles › "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries

"API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries

Missing API key errors — "API key is required", "API key not found", "No API key was set", "<PROVIDER>_API_KEY is not set" — come from a library's own credential resolution code before any request leaves the machine. This article maps the family across 16 open-source repositories: the argument, config, auth-store, and environment chains libraries walk to find a key, why a key that satisfies one subsystem still fails another, and where the key must actually live for Docker, systemd, hooks, serverless, and .env-based setups.

Distilled from 105 documented records across 16 repositories.

Background

Every error in this family is raised by client-side code inside the library, not by the remote API. The check sits in a constructor (AnythingLLM's provider classes, Chroma's embedding functions), a validate_environment hook (LiteLLM's Topaz and Gemini image handlers), a signing utility (easywechat's createV2Signature), or a guardrail initializer (LiteLLM's Cato Networks check), and it fires only after a credential resolution chain comes up empty. The chain has a consistent shape across the family: explicit argument first, then library configuration, then a durable store or persisted config, then one or more environment variables. CodeWhale's provider routes walk override, config table, auth store, environment variable, and external consent in order; LiteLLM's Evals handler checks litellm_params.api_key, then litellm.api_key and litellm.openai_key, then OPENAI_API_KEY. Because these checks run before any network I/O, there is no status code and no server log to inspect; the error text itself, naming an env var or a config table, is the diagnostic.

From the caller's side, the surface varies by ecosystem more than the cause does. The Python members mostly raise ValueError (Chroma, LiteLLM's Voyage, Topaz, and WatsonX handlers), easywechat raises InvalidConfigException before any crypto runs, claude-mem fails with a typed error kind (missing_api_key) that callers can catch to degrade to local-only operation, and chatwoot raises a skip exception (CurationSkipped) that callers are told to treat as expected control flow rather than a crash. CodeWhale bails with a formatted remediation message that interpolates the console URL, the auth-set command, the env var label, and the config table name. Timing varies as well: some members validate eagerly at construction or startup, so selecting the provider fails immediately, while others check lazily at first request or when building headers, so the failure surfaces mid-operation.

Where the key is expected to live is the biggest axis of variation. Environment variables dominate, but each library picks its own names and precedence: LiteLLM's WatsonX token exchange accepts WX_API_KEY, WATSONX_API_KEY, WATSONX_APIKEY, or WATSONX_ZENAPIKEY; its Gemini chat and interactions paths try GOOGLE_API_KEY before GEMINI_API_KEY; its Gemini image_edit path consults GEMINI_API_KEY only. Other members read config files (~/.codewhale/config.toml provider tables, chatwoot's InstallationConfig rows, GitButler's git config), UI-stored settings (AnythingLLM's Community Hub connection key, OpenHuman's Connections panel), or per-request fields (LiteLLM's litellm_params_template on Gemini managed-agent routes, where the proxy's env fallback is restricted to admins by design). Persisted configurations add their own wrinkle: Chroma embedding functions store only the env var name and never persist the api_key argument, so rehydrating a saved config on a machine without the env var fails by design.

Two cross-cutting traps explain most real-world hits. First, the process that needs the key is rarely the shell where it was exported: hooks run from the host application's launch environment, systemd and PM2 services do not inherit the interactive shell, Docker needs explicit pass-through, and .env loaders have quirks — one parser in the family reads only the script directory, fills undefined variables only, and rejects lines with spaces around the equals sign. Second, keys are scoped per subsystem: a working WeChat Pay v3 setup still trips the separate v2 secret key check, multimodal embedding has its own key check even when text embeddings work, the Gemini embedding variable differs from the Gemini chat variable, and Kimi Code membership-plan routes never import Kimi CLI credentials or accept a generic Moonshot platform key. Where records disagree, such as whether GOOGLE_API_KEY satisfies a given Gemini path, the behavior is path- and library-specific, and the error message is the authority.

Common causes

What usually fixes it

Go deeper

Documented occurrences

…and 85 more across the corpus — use search.

Honest provenance: generated on 2026-08-22 from AI-assisted analysis of the linked records. See how records are made.