# How Open Notebook Implements Fallback Logic for Credential Providers: Database-First with Environment Variable Fallback

> Discover how Open Notebook implements fallback logic for credential providers with a database-first approach and environment variable fallback for seamless secret management and backward compatibility.

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

---

**Open Notebook uses a database-first credential system that automatically falls back to environment variables when database entries are missing, ensuring backward compatibility while centralizing secret management in SurrealDB.**

Open Notebook is a privacy-first AI research assistant built with FastAPI and SurrealDB. Its fallback logic for credential providers allows teams to store AI provider secrets securely in the database while maintaining compatibility with legacy code that expects traditional environment variables. This architecture ensures seamless migration from env-var-only setups to a managed credential system without breaking existing integrations.

## Architecture Overview

The credential system spans three layers. The **Credential domain model** ([`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py)) defines encrypted storage for API keys and provider configurations. The **key provider module** ([`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py)) implements the fallback logic that bridges database records and environment variables. Finally, the **API service layer** ([`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py)) handles migrations and model discovery, ensuring that both database credentials and environment variables can coexist.

## The Credential Model: Database as Source of Truth

The `Credential` class in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) serves as the single source of truth for all AI provider authentication. Key implementation details include:

- **Dynamic configuration**: The `CONFIG_EXTRAS` mechanism mirrors top-level fields (e.g., `num_ctx`) into a flexible `config` dictionary, allowing new provider settings without database migrations.
- **Encryption at rest**: The `api_key` field is encrypted before storage; decryption occurs during read operations in `_from_db_row`, `get`, and `get_all` methods.
- **Esperanto integration**: The `to_esperanto_config()` method builds the configuration dictionary required by the Esperanto library's `AIFactory`, pulling values from both the model and its `config` bag.

## Fallback Logic Implementation

The fallback mechanism is centralized in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) through the `provision_provider_keys()` function. This module implements a **database-first, environment-variable-second** strategy.

### Database-First Lookup

The `_get_default_credential(provider)` function queries SurrealDB for the first credential record matching the requested provider. If found, the system decrypts the API key and prepares it for use.

### Environment Variable Fallback

When no database credential exists, `get_api_key(provider)` consults the `PROVIDER_CONFIG` mapping—a dictionary that maps provider names to their traditional environment variable names (e.g., `OPENAI_API_KEY`, `GROQ_API_KEY`). The function returns the env-var value if the database lookup fails.

### Provisioning Environment Variables

The `_provision_simple_provider()` function bridges the gap by copying database credentials into `os.environ`. This ensures that downstream libraries like Esperanto—which often read standard environment variables—work transparently whether the credential originated from the database or the environment. For complex providers like Azure or Vertex AI, dedicated provisioning helpers set mode-specific variables such as `AZURE_OPENAI_ENDPOINT_LLM`.

## Provider Availability and Model Discovery

The fallback logic determines provider availability in [`api/routers/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/models.py). The system considers a provider available if either condition is true:

```python
has_cred = await _check_provider_has_credential(provider)
has_env = os.environ.get(env_var) is not None
provider_status[provider] = has_cred or has_env

```

Before executing model discovery, the router calls `await provision_provider_keys(provider)` to populate environment variables from the database. The `discover_with_config` function in [`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py) then builds requests using `cred.to_esperanto_config()`, enabling seamless operation regardless of whether credentials are stored in the database or environment.

## Migration Strategies for Legacy Credentials

Open Notebook provides two migration commands in [`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py) for teams transitioning from environment-variable-based setups:

- **`migrate_from_provider_config()`**: Migrates legacy singleton `ProviderConfig` entries into individual `Credential` rows while preserving existing model links.
- **`migrate_from_env()`**: Scans `PROVIDER_ENV_CONFIG` for configured environment variables, creates encrypted `Credential` records for each provider, and links unassigned models.

Both migrations invoke `require_encryption_key()` to ensure `OPEN_NOTEBOOK_ENCRYPTION_KEY` is present before storing any secrets.

## Practical Implementation Examples

### Provision Provider Keys from Database

Use `provision_provider_keys` to load database credentials into the environment before calling AI libraries:

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

# Load OpenAI credentials from DB into os.environ

await provision_provider_keys("openai")

# Verify the key is available

print(os.getenv("OPENAI_API_KEY"))  # Decrypted value from database

```

### Discover Models with Fallback Support

This pattern is used in [`api/routers/models.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/models.py) to handle discovery requests:

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

async def discover_models(provider: str):
    # Ensures env-vars are set from DB or falls back to existing env-vars

    await provision_provider_keys(provider)
    
    # Calls external API using configured credentials

    models = await discover_provider_models(provider)
    return [{"name": m.name, "type": m.model_type} for m in models]

```

### Migrate Environment Variables to Database

Run the migration utility to convert existing env-vars to encrypted database records:

```bash

# Requires OPEN_NOTEBOOK_ENCRYPTION_KEY to be set

uv run python -m api.credentials_service migrate_from_env

```

This command creates `Credential` records from values found in `PROVIDER_ENV_CONFIG`, encrypts the API keys, and associates orphaned models with the new credentials.

## Summary

- **Database-first storage**: All provider credentials live in SurrealDB with encryption at rest, managed through the `Credential` model in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py).
- **Transparent fallback**: 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) queries the database first, then falls back to environment variables via `PROVIDER_CONFIG`.
- **Legacy compatibility**: Migration utilities in [`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py) allow zero-downtime transitions from environment-variable setups to database-managed credentials.
- **Unified interface**: The `to_esperanto_config()` method ensures the Esperanto AI library receives consistent configuration regardless of credential source.

## Frequently Asked Questions

### How does Open Notebook prioritize between database credentials and environment variables?

The system always checks the SurrealDB database first via `_get_default_credential()`. Only if no credential exists for the provider does it fall back to reading the environment variable defined in `PROVIDER_CONFIG`. When database credentials are found, they are copied into `os.environ` to ensure downstream libraries see the most recent values.

### Is the fallback mechanism secure for multi-tenant deployments?

Yes. The fallback logic in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py) operates within the application's memory space and does not persist database credentials to external configuration files. Credentials are decrypted only when needed and loaded into the process environment temporarily. For multi-tenant scenarios, each request operates with isolated credential contexts.

### Can I use the fallback logic to test providers without adding them to the database?

Absolutely. If you set the standard environment variable (e.g., `OPENAI_API_KEY`) in your shell or container configuration, Open Notebook will use it when no database entry exists. This allows testing and development without running migrations, while production deployments can rely on database-managed credentials for better security and auditing.

### What encryption method protects API keys stored in the database?

API keys are encrypted using the key derived from `OPEN_NOTEBOOK_ENCRYPTION_KEY` before storage in SurrealDB. The `Credential` class handles decryption transparently during read operations in methods like `_from_db_row` and `get_all`, ensuring that plaintext values never persist in the database and are only available in memory during runtime.