# How the Database-First Credential Provisioning Pattern Works in Open Notebook

> Discover how the database-first credential provisioning pattern in Open Notebook dynamically injects AI provider credentials via key_provider.py. Learn to securely manage secrets.

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

---

**The database-first credential provisioning pattern stores AI provider credentials in an encrypted database table and dynamically injects them into environment variables at runtime, falling back to existing environment variables when no database record is found.**

Open Notebook implements a secure, flexible approach to managing AI service credentials through the [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py) module. Instead of relying solely on static environment variables, the system queries the database for provider credentials, decrypts them on demand, and provisions them into the process environment before instantiating AI models via the Esperanto library.

## Architecture Overview

The pattern centers on a **runtime provisioning layer** that bridges the database and the AI factory. When a model needs initialization, the system first checks the `credential` table for the requested provider. If a record exists, the encrypted API key and configuration fields are decrypted and mapped to the specific environment variables expected by the underlying SDK (OpenAI, Azure, Vertex, etc.). This ensures that sensitive credentials remain encrypted at rest while remaining accessible to the application without manual configuration files.

## Core Implementation in [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py)

The implementation resides in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) and consists of a lookup layer, a configuration mapping, and specialized provisioning handlers for different provider types.

### Database Lookup and Decryption

The `_get_default_credential()` function queries the `Credential` model via `Credential.get_by_provider()` (defined in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) at lines 39–44). This method retrieves the first credential record matching the requested provider name and automatically decrypts the stored API key using the application's encryption key.

```python

# From open_notebook/domain/credential.py

credential = await Credential.get_by_provider("openai")

# Returns decrypted credential with api_key accessible

```

### Provider-to-Environment Mapping

`PROVIDER_CONFIG` serves as the single source of truth that links provider identifiers to their corresponding environment variable names. This dictionary handles the translation between Open Notebook's internal provider names (e.g., `"openai"`) and the environment variables that the Esperanto library and underlying SDKs expect (e.g., `OPENAI_API_KEY`).

### Simple Provider Provisioning

For providers requiring only an API key, `_provision_simple_provider()` (lines 13–42) handles the provisioning:

1. Retrieves the credential from the database
2. Sets the primary API key environment variable
3. Optionally sets a base URL if specified in the credential record

```python

# Simplified logic from key_provider.py

os.environ[env_var] = credential.api_key
if credential.api_base:
    os.environ[f"{prefix}_API_BASE"] = credential.api_base

```

### Complex Provider Handlers

Specialized functions handle multi-variable providers:

- **`_provision_vertex()`**: Extracts `project`, `location`, and `credentials_path` from the database record and sets `VERTEX_PROJECT`, `VERTEX_LOCATION`, and `GOOGLE_APPLICATION_CREDENTIALS`
- **`_provision_azure()`**: Maps the credential to `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_ENDPOINT`, and `AZURE_OPENAI_API_VERSION`
- **`_provision_openai_compatible()`**: Handles generic OpenAI-compatible endpoints with custom base URLs

## Runtime Execution Flow

The public entry point `provision_provider_keys(provider)` orchestrates the provisioning sequence:

1. **Normalization**: Standardizes the provider name (e.g., converting "azure_openai" to "azure")
2. **Dispatch**: Routes to the appropriate helper based on provider complexity
3. **Environment Injection**: Writes decrypted values to `os.environ`
4. **Fallback**: If no database record exists, the function preserves existing environment variables (lines 89–106), ensuring backward compatibility

This function is typically invoked in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) (lines 135–140) before model creation, or via API endpoints in [`api/routers/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/models.py) (lines 504–534).

```python

# Runtime usage pattern

await provision_provider_keys("openai")  # DB → env vars

model = await AIFactory.create_language(..., provider="openai")

```

## Practical Code Examples

### Provisioning a Single Provider at Runtime

Use `provision_provider_keys()` when initializing specific models to ensure credentials are loaded before instantiation:

```python
from open_notebook.ai.key_provider import provision_provider_keys

async def init_model():
    # Load DB-stored key for OpenAI (if present)

    await provision_provider_keys("openai")
    # Now the environment contains OPENAI_API_KEY → model can be created

    model = await Model.create(
        name="gpt-4",
        provider="openai",
        credential=await Credential.get_by_provider("openai")[0].id,
    )
    return model

```

### Bulk Provisioning at Startup

The `provision_all_keys()` function iterates over every entry in `PROVIDER_CONFIG` and calls `provision_provider_keys()` for each provider plus complex variants:

```python
from open_notebook.ai.key_provider import provision_all_keys

async def startup():
    results = await provision_all_keys()
    # results is a dict like {"openai": True, "vertex": False, ...}

    print("Credential provisioning:", results)

```

**Note:** This approach is discouraged for per-request use because stale environment variables could persist after a credential is deleted from the database.

### Azure OpenAI Configuration

Complex providers requiring multiple configuration parameters are handled automatically:

```python
from open_notebook.ai.key_provider import provision_provider_keys

async def use_azure():
    await provision_provider_keys("azure")
    # Environment now contains:

    #   AZURE_OPENAI_API_KEY,

    #   AZURE_OPENAI_ENDPOINT,

    #   AZURE_OPENAI_API_VERSION, …

    model = await Model.create(name="gpt-35-turbo", provider="azure")
    return model

```

## Summary

- **Database-First Pattern**: Credentials are stored encrypted in the `credential` table and decrypted only at runtime via `Credential.get_by_provider()`
- **Environment Injection**: The `provision_provider_keys()` function in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) maps database records to environment variables expected by AI SDKs
- **Provider Flexibility**: Simple providers use `_provision_simple_provider()` while complex ones (Azure, Vertex) use specialized handlers that set multiple environment variables
- **Backward Compatibility**: If no database record exists, the system falls back to existing environment variables without error
- **Security**: Credentials remain encrypted at rest and are only exposed in memory during the active process session

## Frequently Asked Questions

### What happens if no credential exists in the database for a provider?

If `provision_provider_keys()` cannot find a credential record for the requested provider, it silently falls back to the existing environment variables (lines 89–106 in [`key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/key_provider.py)). This preserves backward compatibility with traditional environment-based configuration while allowing seamless migration to database-stored credentials.

### How are credentials secured in the database?

The `Credential` model in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) stores API keys in an encrypted format. When retrieved via `get_by_provider()`, the model automatically decrypts the sensitive fields using the application's configured encryption key, ensuring that plaintext secrets never persist in the database.

### Why inject credentials into environment variables instead of passing them directly to the AI factory?

The database-first credential provisioning pattern uses environment variables to maintain compatibility with the Esperanto library and underlying AI SDKs (OpenAI, Azure, etc.), which expect configuration through specific environment variable names. This abstraction allows Open Notebook to support multiple credential sources while leveraging existing SDK authentication mechanisms without modifying third-party library code.

### When should I use `provision_all_keys()` versus `provision_provider_keys()`?

Use `provision_provider_keys()` for specific provider initialization to avoid polluting the environment with unused credentials. Use `provision_all_keys()` only during application startup to preload all configured providers, but avoid it in per-request contexts because environment variables persist across requests and could retain stale credentials after database deletion.