How Open Notebook Implements Fallback Logic for Credential Providers: Database-First with Environment Variable Fallback
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) defines encrypted storage for API keys and provider configurations. The key provider module (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) 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 serves as the single source of truth for all AI provider authentication. Key implementation details include:
- Dynamic configuration: The
CONFIG_EXTRASmechanism mirrors top-level fields (e.g.,num_ctx) into a flexibleconfigdictionary, allowing new provider settings without database migrations. - Encryption at rest: The
api_keyfield is encrypted before storage; decryption occurs during read operations in_from_db_row,get, andget_allmethods. - Esperanto integration: The
to_esperanto_config()method builds the configuration dictionary required by the Esperanto library'sAIFactory, pulling values from both the model and itsconfigbag.
Fallback Logic Implementation
The fallback mechanism is centralized in 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. The system considers a provider available if either condition is true:
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 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 for teams transitioning from environment-variable-based setups:
migrate_from_provider_config(): Migrates legacy singletonProviderConfigentries into individualCredentialrows while preserving existing model links.migrate_from_env(): ScansPROVIDER_ENV_CONFIGfor configured environment variables, creates encryptedCredentialrecords 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:
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 to handle discovery requests:
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:
# 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
Credentialmodel inopen_notebook/domain/credential.py. - Transparent fallback: The
provision_provider_keysfunction inopen_notebook/ai/key_provider.pyqueries the database first, then falls back to environment variables viaPROVIDER_CONFIG. - Legacy compatibility: Migration utilities in
api/credentials_service.pyallow 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 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →