How `key_provider.py` Enforces a Database-First Fallback Strategy for API Keys in Open Notebook

open_notebook/ai/key_provider.py resolves API credentials by checking the application database first and only falling back to environment variables when no matching secret exists in the Credential model.

The lfnovo/open-notebook repository centralizes LLM provider secrets inside an internal credential store rather than scattering them across configuration files. The open_notebook/ai/key_provider.py module defines the fallback strategy for API keys, prioritizing database records over environment variables. This design lets administrators rotate secrets at runtime through the UI while keeping legacy container deployments that rely on os.environ fully functional.

How _get_default_credential() Queries the Database

The private helper _get_default_credential() (lines 76‑84 of open_notebook/ai/key_provider.py) executes the initial search for every provider request. It queries the Credential model—defined in open_notebook/domain/credential.py—for the first record whose provider name matches the argument. When a row exists, the function returns the credential object; otherwise it returns None.

The get_api_key() Hierarchy: Database Credentials Before Environment Variables

get_api_key() (lines 87‑102) is the core routine that downstream components invoke to fetch a working secret. The function implements a strict resolution chain: database credentialenvironment variableNone.

Returning a Database Secret

If _get_default_credential() returns a record whose api_key field is non‑empty, get_api_key() extracts that value and returns it immediately (lines 98‑100). Because this branch executes before any environment lookup, a database key always overrides an identically named shell variable without extra logic.

Falling Back to PROVIDER_CONFIG Environment Variables

When the database contains no matching credential—or the stored key is blank—the routine falls back to the environment variable mapped by PROVIDER_CONFIG (lines 103‑108). This preserves backward compatibility for Docker, systemd, or serverless deployments that inject secrets through the shell rather than the UI.

Returning None When Every Source Fails

If neither the database nor the fallback environment variable yields a value, get_api_key() returns None (line 110). Callers can then decide whether to skip the provider or raise a configuration error.

Provisioning Helpers and Controlled Side Effects

In addition to retrieval, the module can populate the current process environment from the database. Every provisioning helper follows the same database‑first rule, but they only mutate os.environ when a stored credential actually exists.

How _provision_simple_provider() Populates os.environ

Functions such as _provision_simple_provider() (lines 113‑124) and the specialized _provision_azure() and _provision_vertex() variants begin by reading the database. On success, they write the corresponding values into os.environ. If the lookup returns nothing, the helper hits an early return at line 129, leaving existing environment variables untouched.

provision_provider_keys() as the Public Entry Point

provision_provider_keys() (lines 46‑81) dispatches to the correct provisioning helper based on the provider name. As stated in its docstring, the function guarantees that environment variables are set only when a matching credential exists in the database. If no record is found, the function returns False and the process continues with whatever env-based configuration was already present.

Practical Code Examples

The snippets below demonstrate the database‑first resolution order in practice.

from open_notebook.ai.key_provider import get_api_key

# Database is checked first; if it is empty, the OPENAI_API_KEY

# environment variable is used as a fallback.

api_key = await get_api_key("openai")
print(api_key)
from open_notebook.ai.key_provider import provision_provider_keys

# AZURE_OPENAI_API_KEY and related env vars are overwritten

# ONLY when a matching Azure credential exists in the database.

await provision_provider_keys("azure")

Summary

  • Database credentials always take precedence. get_api_key() queries the Credential model before it ever inspects os.environ.
  • Environment variables serve as a transparent fallback. If the database record is missing or its api_key field is empty, the provider mapping in PROVIDER_CONFIG directs the search to the shell environment.
  • Side effects are conditional. Provisioning helpers such as _provision_simple_provider() only overwrite os.environ when a database secret is actually present.
  • Public API is explicit. provision_provider_keys() dispatches internally and preserves existing environment state when no credential is stored.

Frequently Asked Questions

Why does key_provider.py prefer the database over environment variables?

Prioritizing the database lets users rotate or revoke API keys through the Open Notebook UI without redeploying containers. Environment variables remain available as a fallback for backward compatibility, but the central credential store guarantees that the most recently saved secret is always used at runtime.

What happens if a database credential exists but its api_key field is empty?

get_api_key() treats an empty database field as a missing value. It proceeds to the environment variable defined in PROVIDER_CONFIG. If that variable is also absent, the function ultimately returns None.

Does provision_provider_keys() overwrite existing environment variables?

It only mutates os.environ when a corresponding non‑empty credential exists in the database. If no credential is found, the helper returns early and leaves the current environment untouched, preserving any pre-existing variables.

Which source files define the Credential model used by key_provider.py?

The Credential domain model is defined in open_notebook/domain/credential.py. The open_notebook/ai/key_provider.py module imports this model to perform lookups, while provider-specific configuration mappings live inside the same key-provider file.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →