Implementation:OpenHands OpenHands SaasSettingsStore
| Knowledge Sources | |
|---|---|
| Domains | Storage, Settings, SaaS_Infrastructure |
| Last Updated | 2026-02-11 21:00 GMT |
Overview
Multi-table settings composition store with LiteLLM key management and field-level encryption, provided by the OpenHands enterprise storage layer.
Description
SaasSettingsStore manages user and organization settings by composing data from multiple database tables into a unified settings object and distributing writes back across those tables.
The load() method reads from three sources -- the Org table, the User table, and the OrgMember table -- and merges them into a single settings dictionary. This composition allows settings to have different scopes (organization-wide vs. user-specific vs. membership-specific) while presenting a unified interface to callers.
The store() method performs the reverse operation: it takes a unified settings dictionary and distributes the values to the appropriate tables based on which fields belong where.
A key feature is _ensure_api_key(), which manages LiteLLM API keys. When settings include LLM configuration, this method ensures a valid LiteLLM API key exists for the user, creating one if necessary. This integrates the settings store with the LiteLLM proxy for model routing.
The store encrypts sensitive fields before persisting them. The ENCRYPT_VALUES list defines which fields require encryption:
ENCRYPT_VALUES = ['llm_api_key', 'llm_api_key_for_byor', 'search_api_key']
These fields are encrypted at rest and decrypted when loaded, ensuring API keys and credentials are never stored in plaintext.
Usage
Use SaasSettingsStore in the settings API endpoints to load and persist user/org settings. Call load() to retrieve the composed settings for display in the UI. Call store() after the user saves changes to distribute updates to the appropriate tables and ensure LiteLLM keys are provisioned.
Code Reference
Source Location
- Repository: OpenHands
- File: enterprise/storage/saas_settings_store.py
- Lines: 1-278
Signature
class SaasSettingsStore:
ENCRYPT_VALUES = ['llm_api_key', 'llm_api_key_for_byor', 'search_api_key']
def __init__(self, session: Session, org_id: str, config: AppConfig):
...
def load(self, user_id: str) -> dict:
"""Composes settings from Org + User + OrgMember tables into a unified dict."""
...
def store(self, user_id: str, settings: dict) -> None:
"""Distributes settings to appropriate tables and ensures LiteLLM key exists."""
...
def _ensure_api_key(self, user_id: str, settings: dict) -> str:
"""Creates or retrieves a LiteLLM API key for the user."""
...
Import
from enterprise.storage.saas_settings_store import SaasSettingsStore
I/O Contract
Inputs
Constructor
| Name | Type | Required | Description |
|---|---|---|---|
| session | Session | Yes | SQLAlchemy database session for executing queries |
| org_id | str | Yes | Organization ID that scopes the settings |
| config | AppConfig | Yes | Application configuration containing encryption keys and LiteLLM settings |
load()
| Name | Type | Required | Description |
|---|---|---|---|
| user_id | str | Yes | The user whose composed settings to load |
store()
| Name | Type | Required | Description |
|---|---|---|---|
| user_id | str | Yes | The user whose settings to persist |
| settings | dict | Yes | Unified settings dictionary; values are distributed to appropriate tables |
_ensure_api_key()
| Name | Type | Required | Description |
|---|---|---|---|
| user_id | str | Yes | The user to provision a LiteLLM key for |
| settings | dict | Yes | The current settings (used to determine if LLM config is present) |
Outputs
| Method | Return Type | Description |
|---|---|---|
| load() | dict | Unified settings dictionary composed from Org, User, and OrgMember tables (encrypted fields are decrypted) |
| store() | None | Distributes settings to tables, encrypts sensitive fields, and provisions LiteLLM key if needed |
| _ensure_api_key() | str | The LiteLLM API key (existing or newly created) |
Encrypted Fields
| Field Name | Description |
|---|---|
| llm_api_key | Primary LLM provider API key |
| llm_api_key_for_byor | Bring-your-own-runtime LLM API key |
| search_api_key | Search provider API key (e.g., for web search integration) |
Usage Examples
Loading Composed Settings
from enterprise.storage.saas_settings_store import SaasSettingsStore
settings_store = SaasSettingsStore(
session=db_session,
org_id="org-123",
config=app_config
)
# Load unified settings from multiple tables
settings = settings_store.load(user_id="user-456")
# Returns composed dict:
# {
# "llm_model": "claude-sonnet-4-20250514", (from Org table)
# "llm_api_key": "sk-decrypted-key", (from OrgMember, decrypted)
# "theme": "dark", (from User table)
# ...
# }
Storing Settings with LiteLLM Key Provisioning
# Store updated settings -- values are distributed to the right tables
settings_store.store(
user_id="user-456",
settings={
"llm_model": "claude-sonnet-4-20250514",
"llm_api_key": "sk-new-api-key", # Will be encrypted at rest
"search_api_key": "search-key-abc", # Will be encrypted at rest
"theme": "dark",
"language": "en"
}
)
# _ensure_api_key() is called internally to provision/verify the LiteLLM key
Related Pages
Related Implementations
- Implementation:OpenHands_OpenHands_SaasSecretsStore -- User custom secrets with same Fernet encryption pattern