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

> Discover how Open Notebook's key_provider.py prioritizes database API keys, offering a robust fallback strategy by checking environment variables only when necessary.

- Repository: [Luis Novo/open-notebook](https://github.com/lfnovo/open-notebook)
- Tags: internals
- Published: 2026-06-06

---

**[`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/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 credential** → **environment variable** → **`None`**.

### 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.

```python
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)

```

```python
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`](https://github.com/lfnovo/open-notebook/blob/main/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`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py)?

The `Credential` domain model is defined in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py). The [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/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.