# How Open-Notebook's Multi-Provider Credential System Integrates with Esperanto

> Discover how Open-Notebook's multi-provider credential system seamlessly integrates with Esperanto. Encrypt API keys, convert to Esperanto configs, and bypass environment variables. Learn more!

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

---

**Open-Notebook's multi-provider credential system stores encrypted API keys per provider and converts them to Esperanto-compatible configuration dictionaries via `Credential.to_esperanto_config()`, eliminating the need for environment variables while maintaining backward compatibility through a fallback mechanism.**

The `lfnovo/open-notebook` repository implements a database-first approach to managing AI provider credentials, storing one encrypted record per provider instead of relying on global environment variables. This multi-provider credential system seamlessly integrates with Esperanto—the library's AI abstraction layer—by transforming stored database records into provider-specific configuration dictionaries that Esperanto's `AIFactory` can consume directly.

## Architecture of the Multi-Provider Credential System

### Database-First Design with Per-Provider Storage

Instead of a single global configuration, Open-Notebook stores one **Credential** record per AI provider in SurrealDB. Each record contains the raw API key encrypted at rest, alongside provider-specific settings such as `base_url`, `endpoint`, and `project`. This design allows multiple configurations for the same provider (e.g., separate OpenAI accounts for different projects) while maintaining strict isolation between credentials.

### Encryption and the Credential 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 and decryption using a symmetric key stored in the application configuration. When a model request occurs, the system calls `Credential.get()` to retrieve the database row and decrypts the `api_key` field before converting it to Esperanto format.

## The Esperanto Integration Flow

### Step 1: Model Resolution via ModelManager

When code requests a model, `ModelManager.get_model()` in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) loads the requested `Model` record from the database. If the model has a `credential` field, the manager calls `Model.get_credential_obj()` to initiate the credential retrieval process. This method returns a fully instantiated `Credential` object ready for configuration conversion.

### Step 2: Credential Decryption and Retrieval

The `Credential.get()` method fetches the record from SurrealDB using the stored credential ID and decrypts the API key using the application's encryption key. This decrypted credential object contains all provider-specific configuration values needed to instantiate the AI client, but in Open-Notebook's internal format.

### Step 3: Config Transformation via to_esperanto_config()

The critical integration point occurs in `Credential.to_esperanto_config()` (lines 102-138 of [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py)). This method builds a plain dictionary matching Esperanto's expected `config` format, mapping internal fields like `api_key`, `base_url`, and `endpoint` to the exact keys Esperanto requires. The method ensures Esperanto receives a fully-populated configuration dictionary and therefore does not need to read environment variables for that model.

### Step 4: Provider Instantiation via AIFactory

`ModelManager` merges the Esperanto configuration with any additional kwargs, normalizes the provider name (e.g., converting `openai-compatible` to `openai-compatible`), and calls the appropriate `AIFactory.create_*` method—such as `create_language()`, `create_embedding()`, or `create_speech_to_text()`. Esperanto instantiates the concrete provider model using the supplied configuration, caching it internally for subsequent requests.

## Environment Variable Fallback for Backward Compatibility

When a model lacks a linked credential, Open-Notebook falls back to the historic environment-variable behavior. The system calls `provision_provider_keys(provider)` from [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py), which reads the first `Credential` record for that provider (if any) and writes the relevant values into `os.environ`. This ensures that providers like Azure or Vertex—which Esperanto may still expect to read from the environment—continue to function while the database remains the source of truth.

## Practical Implementation Examples

### Creating a Credential via API

Create a new credential record through the REST API:

```python
import httpx

payload = {
    "name": "OpenAI-Prod",
    "provider": "openai",
    "modalities": ["language", "embedding"],
    "api_key": "sk-xxxxxxxxxxxxxxxxxxxx"
}

resp = httpx.post("http://localhost:5055/api/credentials", json=payload)
print(resp.json())

```

The API stores the key encrypted at rest in the `credential` table.

### Linking a Model to a Credential

Associate a credential with a specific model to enable the Esperanto integration:

```json
POST /api/models
{
  "name": "gpt-4o",
  "provider": "openai",
  "type": "language",
  "credential": "<credential-record-id>"
}

```

### Using Models in Application Code

Access models through `ModelManager`, which automatically handles the credential-to-Esperanto conversion:

```python
from open_notebook.ai.models import model_manager

# Returns an Esperanto LanguageModel ready for LangChain usage

chat_model = await model_manager.get_default_model("chat")

response = await chat_model.ainvoke(
    {"messages": [{"role": "user", "content": "Hello"}]}
)
print(response)

```

Behind the scenes, this triggers: `get_model()` → `get_credential_obj()` → `Credential.get()` → `to_esperanto_config()` → `AIFactory.create_language()`.

### Manual Environment Provisioning

For legacy compatibility, manually provision environment variables when no credential is linked:

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

# Sets AZURE_OPENAI_* env vars from the first matching credential in DB

await provision_provider_keys("azure")

```

## Summary

- Open-Notebook's multi-provider credential system stores one encrypted **Credential** record per AI provider in SurrealDB, replacing global environment variable configurations.
- The `Credential.to_esperanto_config()` method in [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py) transforms database records into Esperanto-compatible configuration dictionaries, allowing direct instantiation via `AIFactory`.
- **ModelManager** in [`open_notebook/ai/models.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/models.py) orchestrates the flow: loading models, retrieving credentials, and converting them to Esperanto format before calling the appropriate factory method.
- If no credential is linked to a model, the system falls back to `provision_provider_keys()` in [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py), which copies credential values to environment variables for backward compatibility.
- This architecture enables multiple concurrent configurations per provider while maintaining seamless integration with the Esperanto abstraction layer.

## Frequently Asked Questions

### How does Open-Notebook encrypt API keys in the multi-provider credential system?

API keys are encrypted at rest using symmetric encryption implemented in the `Credential` class within [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py). When storing a new credential, the system encrypts the raw `api_key` field before persisting to SurrealDB. During retrieval, `Credential.get()` automatically decrypts the key before converting it to Esperanto configuration format via `to_esperanto_config()`.

### What happens if a model has no linked credential in Open-Notebook?

When `ModelManager` encounters a model without a linked credential, it invokes `provision_provider_keys()` from [`open_notebook/ai/key_provider.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/ai/key_provider.py). This function retrieves the first available credential for that provider from the database and writes the values into `os.environ`. Esperanto then reads these environment variables to instantiate the provider, maintaining backward compatibility with legacy code paths that expect environment-based configuration.

### How does the Credential.to_esperanto_config() method work?

The `to_esperanto_config()` method (lines 102-138 of [`open_notebook/domain/credential.py`](https://github.com/lfnovo/open-notebook/blob/main/open_notebook/domain/credential.py)) constructs a plain Python dictionary containing exactly the keys Esperanto expects: `api_key`, `base_url`, `endpoint`, and provider-specific settings. This configuration dictionary is passed directly to Esperanto's `AIFactory.create_*` methods, eliminating the need for Esperanto to access environment variables or external configuration files for that specific model instance.

### Can I use multiple credentials for the same provider in Open-Notebook?

Yes. Because the system stores one **Credential** record per configuration rather than one per provider type, you can create multiple credentials for the same provider (e.g., "OpenAI-Prod" and "OpenAI-Dev"). Each model references a specific credential record via its `credential` field, allowing different models to use different API keys or endpoints while sharing the same provider type. The `ModelManager` resolves the correct credential for each model request independently.