# How Open Notebook Manages AI Provider Credentials: Architecture and Security

> Learn how Open Notebook secures AI provider credentials using SurrealDB encryption and FastAPI. Discover its architecture and never expose raw API keys.

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

---

**Open Notebook stores AI provider credentials as individual encrypted records in SurrealDB, exposing them via a FastAPI router while never returning raw API keys in API responses.**

The `lfnovo/open-notebook` project implements a secure, domain-driven approach to AI provider credential management that replaces global configuration files with encrypted database records. Each credential is stored as a first-class entity that integrates with the Esperanto multi-provider library, supporting automatic encryption at rest and granular control over authentication lifecycle. This architecture ensures that sensitive API keys remain encrypted while remaining accessible to the application for model discovery and inference.

## Core Architecture of AI Provider Credential Management

### The Credential Domain Model

The `Credential` class in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) defines the central data structure for AI provider authentication. As a concrete SurrealDB record, it encapsulates the provider name, API key, optional base URLs, and provider-specific configuration options like `num_ctx`. The model implements `to_esperanto_config()`, which constructs the configuration dictionary that the Esperanto library expects, enabling seamless integration with any supported AI provider.

### Encryption and Security Implementation

Security relies on Fernet symmetric encryption via the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable defined in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py). Before persistence, the `_prepare_save_data` method encrypts the API key using `encrypt_value`, while retrieval methods including `get`, `get_all`, and `_from_db_row` automatically decrypt the value using `decrypt_value`. The API key is wrapped in `pydantic.SecretStr` to prevent accidental logging or serialization of plaintext secrets.

### Legacy Migration from ProviderConfig

The legacy singleton pattern in [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/provider_config.py) previously aggregated multiple `ProviderCredential` objects per provider. The system provides a migration path via the `/credentials/migrate-from-provider-config` endpoint, which reads the legacy configuration, decrypts any stored keys, and creates individual `Credential` records in the SurrealDB table.

## API Layer for Credential Management

### CRUD Operations via FastAPI

The [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) module exposes REST endpoints for full lifecycle management of AI provider credentials. The `POST /credentials` endpoint validates endpoint URLs, ensures the encryption key is present via `require_encryption_key`, and wraps the raw key in `SecretStr` before calling `await cred.save()`. Listing operations return sanitized `CredentialResponse` objects that exclude the raw API key, while `PUT /credentials/{id}` supports partial updates where `None` values clear optional fields.

### Discovery and Model Registration

After credential creation, the `/credentials/{id}/discover` endpoint uses the decrypted key to query the provider for available models. The `get_linked_models()` method queries the `model` table for records where the `credential` field matches the credential ID, enabling the UI to display model counts and relationships. When deleting credentials, the system supports cascading reassignment of linked models via the `migrate_to` query parameter, with fallback to raw database operations if decryption fails due to encryption key rotation.

## Using AI Provider Credentials in Practice

### Creating Credentials Programmatically

You can instantiate and save credentials directly using the domain model:

```python
from open_notebook.domain.credential import Credential
from pydantic import SecretStr

cred = Credential(
    name="Prod‑OpenAI",
    provider="openai",
    modalities=["language", "embedding"],
    api_key=SecretStr("sk‑my‑secret‑key"),
    base_url=None,
    endpoint=None,
    api_version="2023‑08‑15",
    num_ctx=16384,
)
await cred.save()

```

The `save()` call automatically encrypts the API key before persistence.

### Integrating with the Esperanto Library

To use a stored credential for inference, convert it to the Esperanto configuration format:

```python
from open_notebook.domain.credential import Credential

cred = await Credential.get("<credential‑id>")
config = cred.to_esperanto_config()  # Returns dict with decrypted key

# Pass to Esperanto factory or your model initialization

```

This approach bypasses environment variable lookups in favor of the stored credential configuration.

### Listing Credentials via REST API

Retrieve all credentials without exposing sensitive data:

```bash
curl -X GET http://localhost:5055/credentials \
     -H "Authorization: Bearer <token>"

```

The response includes metadata fields like `id`, `name`, `provider`, and `model_count`, but **never** the raw API key.

### Migrating from Legacy Configuration

Convert existing provider configurations to the new credential system:

```bash
curl -X POST http://localhost:5055/credentials/migrate-from-provider-config

```

This endpoint returns a summary of migrated credentials and their new record IDs.

## Summary

- **Encrypted Storage**: API keys are encrypted at rest using Fernet encryption via `OPEN_NOTEBOOK_ENCRYPTION_KEY` and wrapped in `pydantic.SecretStr`.
- **Domain Model**: The `Credential` class in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) handles encryption, decryption, and conversion to Esperanto configuration formats.
- **Secure API**: The FastAPI router in [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) never returns raw API keys in responses, filtering sensitive fields through `CredentialResponse`.
- **Model Linkage**: Credentials maintain relationships with AI models through the `get_linked_models()` method, enabling cascade operations during deletion.
- **Migration Support**: Legacy configurations from [`provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/provider_config.py) migrate to individual `Credential` records via dedicated API endpoints.

## Frequently Asked Questions

### How are API keys encrypted in open-notebook?

API keys are encrypted using Fernet symmetric encryption from the `cryptography` library, implemented in [`open_notebook/utils/encryption.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/utils/encryption.py). The system requires the `OPEN_NOTEBOOK_ENCRYPTION_KEY` environment variable; if absent, keys are stored in plain text with a security warning. All encryption operations happen transparently through the `Credential` model's `_prepare_save_data` and `_from_db_row` methods.

### What happens if the server encryption key changes?

If the encryption key changes, existing credentials cannot be decrypted normally. The deletion and migration endpoints in [`api/routers/credentials.py`](https://github.com/lfnovo/open-notebook/blob/main/api/routers/credentials.py) implement fallback logic that uses raw database queries to retrieve and delete records when decryption fails, allowing administrators to clean up or migrate credentials even after key rotation.

### How do I migrate from the old provider configuration?

Use the `/credentials/migrate-from-provider-config` endpoint, which reads the legacy singleton from [`open_notebook/domain/provider_config.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/provider_config.py), decrypts any stored authentication data, and creates new individual `Credential` records for each provider configuration. This process preserves the original credentials while upgrading to the encrypted record-based architecture.

### Can a single credential support multiple AI providers?

No, each `Credential` record is tied to a specific provider name (e.g., "openai", "anthropic"). However, you can create multiple `Credential` records for the same provider with different configurations, and the system tracks which models are linked to each credential via the `get_linked_models()` method.