# How to Securely Configure Multi-Provider AI Credentials in Open Notebook

> Securely configure multi-provider AI credentials in Open Notebook using encrypted SurrealDB. Rotate credentials at runtime for OpenAI, Anthropic, Google, and Ollama without restarts.

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

---

**Open Notebook stores API keys for OpenAI, Anthropic, Google, and Ollama in an encrypted SurrealDB `credential` table rather than in environment variables, enabling runtime rotation and per-model credential selection without service restarts.**

The `lfnovo/open-notebook` project decouples AI provider secrets from application configuration by persisting them in a structured database. This approach allows you to **securely configure multi-provider AI credentials in database** records, supporting multiple keys per provider and hot-swapping them at runtime. Instead of restarting containers to update keys, you modify records through the REST API or web UI, with the application decrypting secrets only when initializing model calls via the `to_esperanto_config()` method.

## Architecture Overview

### The Credential Domain Model

In [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py), the `Credential` class defines the schema for secrets. It inherits from `BaseRecord`, which automatically assigns a SurrealDB record ID such as `credential:123`. The model stores the **provider** name (e.g., `openai`, `anthropic`), an encrypted **secret** string, optional JSON **config** metadata, and an **is_default** boolean flag. The `__repr__` method deliberately excludes the secret field to prevent accidental leaks in logs.

### Provider Configuration Singleton

The `ProviderConfig` class in [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/provider_config.py) acts as an in-memory registry. It loads credential records from SurrealDB at startup and caches them in RAM. Key methods include `get_default(provider)`, which returns the credential marked as default for a specific AI provider, and `add_config(provider, credential)`, which registers a new key. This singleton pattern ensures that credential lookups do not require repeated database queries during inference.

## Encryption and Security Model

Secrets are never stored in plain text. The [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py) module initializes a Fernet cipher using the `ENCRYPTION_KEY` environment variable. When a credential is created via [`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py), the service encrypts the secret before the `Credential` model is inserted into the `credential` table. Only during model execution does the `to_esperanto_config()` method decrypt the secret, returning a configuration dictionary suitable for the Esperanto AI-provider wrapper.

All endpoints in [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) are protected by the same password middleware used for the API, ensuring only authenticated users can manage secrets.

## Managing Credentials via the API

Creating a credential:

```bash
curl -X POST http://localhost:5055/api/credentials \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "openai",
    "name": "production-key",
    "secret": "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "config": {"org_id": "org-abc123"},
    "is_default": true
  }'

```

Rotating a credential:

```bash
curl -X PATCH http://localhost:5055/api/credentials/credential:42 \
  -H "Content-Type: application/json" \
  -d '{
    "secret": "sk-newkeyyyyyyyyyyyyyyyyyyyyyy",
    "is_default": true
  }'

```

Behind the scenes, [`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py) validates the payload, triggers encryption via [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py), and updates the `ProviderConfig` cache so subsequent requests use the new key immediately.

## Integrating Credentials with AI Models

When a podcast model (or any AI model) needs to instantiate a provider client, it resolves the associated credential. In [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py), the code retrieves the credential object and calls `to_esperanto_config()` to generate a provider-ready dictionary:

```python
from open_notebook.domain.provider_config import ProviderConfig
from open_notebook.ai.provision import provision_langchain_model

credential = ProviderConfig().get_default("openai")
if credential:
    esp_config = credential.to_esperanto_config()
    llm = provision_langchain_model(
        provider=credential.provider,
        model_name="gpt-4o",
        config=esp_config,
    )

```

This pattern ensures that model definitions remain free of hard-coded secrets, referencing only the credential record ID.

## Default Fallbacks and Migration

For legacy models that lack an explicit credential reference, [`open_notebook/podcasts/migration.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/migration.py) provides a helper that auto-links the first available credential for the target provider. This deterministic fallback guarantees that existing deployments continue to function after the database schema migrates to the credential-based system.

## Summary

- Store secrets in the SurrealDB `credential` table via [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) instead of `.env` files to enable runtime updates.
- Encrypt all secrets using Fernet with the `ENCRYPTION_KEY` environment variable handled by [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py).
- Use the `ProviderConfig` singleton in [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/provider_config.py) for in-memory caching and fast lookups.
- Expose CRUD operations through [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) and business logic in [`api/credentials_service.py`](https://github.com/lfnovo/open-notebook/blob/main/api/credentials_service.py).
- Decrypt keys only during model initialization via `to_esperanto_config()` and inject them using `provision_langchain_model` in [`open_notebook/podcasts/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/podcasts/models.py).
- Rotate credentials without restarts using PATCH requests; the cache refreshes automatically.

## Frequently Asked Questions

### How is the encryption key managed?

The application reads the `ENCRYPTION_KEY` environment variable at startup. This key must be a valid Fernet key (32 bytes, base64-encoded). If the variable is missing, the encryption module in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py) raises an error, preventing the application from starting with an insecure configuration.

### Can I use multiple API keys for the same provider?

Yes. The `credential` table supports multiple records per provider. You can designate one as the default using the `is_default` flag, and specific models can reference alternative credentials by ID. The `ProviderConfig` class maintains these mappings in memory for performance.

### What happens if I delete the default credential?

If the default credential is removed, `ProviderConfig.get_default()` returns `None`. Models that rely on the default will fail to initialize until a new default is set or the model is explicitly updated to reference a different credential ID. Always ensure at least one credential exists for providers in active use.

### Are credentials exposed in the web UI or logs?

No. The `Credential` model's `__repr__` excludes the secret field, and the API responses from [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) omit the decrypted secret. The Settings page at `/settings` displays only the credential name, provider, and default status, never the raw API key.