How to Handle Provider Authentication with API Keys in aisuite
aisuite resolves API keys through a three-step chain—explicit configuration, environment variables, and SecretStore fallback—implemented in each provider's resolve_api_key function.
The aisuite library by Andrew Ng provides a unified Python interface for multiple LLM providers. Handling provider authentication with API keys follows a prioritized resolution strategy that checks explicit configuration first, then environment variables, and finally an internal SecretStore. This design ensures flexibility whether you are running scripts locally, deploying to production, or building desktop applications with theSettings UI.
The Three-Step Authentication Resolution Chain
Each provider in aisuite follows a strict hierarchy when resolving credentials. The resolution chain is implemented in the resolve_api_key function found in every provider module.
Step 1: Explicit Configuration via Client
The Client class stores authentication details in a provider_configs dictionary. When you instantiate a provider, any api_key supplied directly to the configuration dictionary takes highest priority. According to the source code in aisuite/client.py, the configure method merges new settings into the existing configuration:
def configure(self, provider_configs: Optional[dict] = None):
if provider_configs is None:
provider_configs = {}
self.provider_configs.update(self._copy_provider_configs(provider_configs))
This stored configuration is later passed to the provider factory in platform/coworker/providers/registry.py, which forwards the api_key argument to the provider's constructor.
Step 2: Environment Variable Lookup
If no explicit key is provided, each provider's resolve_api_key function checks for provider-specific environment variables. The naming convention follows the standard {PROVIDER}_API_KEY format:
- OpenAI:
OPENAI_API_KEY - Anthropic:
ANTHROPIC_API_KEY - Gemini:
GEMINI_API_KEYorGOOGLE_API_KEY
This check occurs in the provider-specific files such as platform/coworker/providers/openai_provider.py, platform/coworker/providers/anthropic_provider.py, and platform/coworker/providers/gemini_provider.py.
Step 3: SecretStore Fallback
If the environment variable is missing, the resolver reads from an in-memory SecretStore. It looks for a profile key formatted as provider:<name> (e.g., provider:openai) and returns the "api_key" field. The SecretStore implementation lives in platform/coworker/secrets.py and is typically populated by the Settings UI in desktop applications, allowing dynamic key configuration without restarting the engine.
Provider-Side Resolution Implementation
The resolution logic lives in each provider module. In platform/coworker/providers/openai_provider.py, the resolve_api_key function implements the chain:
def resolve_api_key(secrets: Any = None) -> Optional[str]:
"""Resolve the OpenAI API key: env `OPENAI_API_KEY` first, else the SecretStore
`provider:openai` profile (`{api_key}`).
"""
import os
key = os.environ.get("OPENAI_API_KEY")
if key:
return key
if secrets is not None:
profile = secrets.get("provider:openai") or {}
return profile.get("api_key") or None
return None
Gemini and Anthropic follow the identical pattern, differing only in their environment variable names.
Lazy Client Initialization
Provider objects do not create the underlying SDK client in __init__. Instead, the _ensure_client method handles lazy initialization on first use. This method, found in platform/coworker/providers/openai_provider.py, applies the resolution chain:
def _ensure_client(self) -> Any:
if self._client is None:
from openai import OpenAI
key = self._api_key or resolve_api_key(self._secrets)
if not key:
raise RuntimeError(
"No model API key configured. Set OPENAI_API_KEY …"
)
kwargs = {"api_key": key}
if self._base_url:
kwargs["base_url"] = self._base_url
self._client = OpenAI(**kwargs)
return self._client
The client is built once and reused for subsequent calls, with the explicit self._api_key checked before falling back to resolve_api_key(self._secrets).
Practical Configuration Methods
Method 1: Environment Variables
Set the standard provider environment variable before importing aisuite:
export OPENAI_API_KEY="sk-my-openai-key"
export ANTHROPIC_API_KEY="sk-ant-key"
The resolver picks up these values automatically when no explicit configuration is provided.
Method 2: Client Configuration Dictionary
Pass API keys directly when constructing the Client for programmatic configuration:
import aisuite as ai
client = ai.Client({
"openai": {"api_key": "sk-my-openai-key"},
"anthropic": {"api_key": "sk-ant-key"},
})
response = client.chat.completions.create(
model="openai:gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
Method 3: SecretStore Integration
For applications using the desktop UI or custom secret management, populate the SecretStore before instantiating providers:
from platform.coworker.secrets import SecretStore
from platform.coworker.providers.openai_provider import OpenAIProvider
# Mimic the Settings UI storing a key
secrets = SecretStore()
secrets.put("provider:openai", {"api_key": "sk-stored-key"})
# Instantiate provider without explicit api_key
provider = OpenAIProvider(secrets=secrets)
client = provider._ensure_client() # Triggers resolve_api_key
print("Using key:", client.api_key) # → sk-stored-key
Method 4: Custom Base URLs
When using local inference servers like Ollama that expose an OpenAI-compatible endpoint, provide both a placeholder key and the custom base_url:
client = ai.Client({
"openai": {
"api_key": "any-placeholder-key", # Ollama ignores the key
"base_url": "http://localhost:11434/v1"
}
})
Key Source Files and Their Roles
platform/coworker/providers/openai_provider.py: Contains theresolve_api_keyfunction and_ensure_clientlazy initialization logic for OpenAI.platform/coworker/providers/anthropic_provider.py: Implements the same resolution pattern for Anthropic withANTHROPIC_API_KEY.platform/coworker/providers/gemini_provider.py: Handles Google Gemini authentication viaGEMINI_API_KEYorGOOGLE_API_KEY.platform/coworker/providers/registry.py: Provider factory that forwardsapi_keyand other config values from the high-level client to individual providers.aisuite/client.py: High-level client interface that stores and managesprovider_configs.platform/coworker/secrets.py: Implements the in-memorySecretStoreused as the final fallback in the resolution chain.
Summary
- aisuite uses a three-tier resolution chain: explicit config → environment variables → SecretStore.
- Each provider implements
resolve_api_keyin its respective file (e.g.,platform/coworker/providers/openai_provider.py). - SDK clients are created lazily via
_ensure_client, checkingself._api_keybefore invoking the resolver. - Configure keys via
OPENAI_API_KEYenvironment variables, Client configuration dictionaries, or theSecretStorefor UI-driven scenarios. - Custom
base_urlvalues can be passed alongside API keys to support local inference servers.
Frequently Asked Questions
What is the priority order for API key resolution in aisuite?
aisuite checks credentials in this strict order: first, any explicit api_key passed to the provider constructor; second, the provider-specific environment variable (e.g., OPENAI_API_KEY); third, the SecretStore entry under provider:<name>. If all three fail, the provider raises a RuntimeError when attempting to create the SDK client.
How do I configure multiple providers with different API keys?
Pass a nested dictionary to ai.Client() where each top-level key is the provider name and the value contains an api_key field. For example: {"openai": {"api_key": "sk-open"}, "anthropic": {"api_key": "sk-ant"}}. The Client stores these in provider_configs and passes them to the appropriate provider factories in platform/coworker/providers/registry.py.
Can I use aisuite with local models like Ollama that do not require real API keys?
Yes. Set the api_key to any placeholder string and provide the base_url pointing to your local server. The OpenAI-compatible provider in platform/coworker/providers/openai_provider.py will initialize the client with your custom base URL, and the local inference engine will ignore the API key value.
Where are API keys stored when using the SecretStore?
The SecretStore defined in platform/coworker/secrets.py is an in-memory key-value store. When the desktop Settings UI saves a key, it calls secrets.put("provider:openai", {"api_key": "..."}). The resolver reads this only if no environment variable exists, making it suitable for runtime configuration without persisting keys to disk or environment variables.
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 →