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_KEY or GOOGLE_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

Summary

  • aisuite uses a three-tier resolution chain: explicit config → environment variables → SecretStore.
  • Each provider implements resolve_api_key in its respective file (e.g., platform/coworker/providers/openai_provider.py).
  • SDK clients are created lazily via _ensure_client, checking self._api_key before invoking the resolver.
  • Configure keys via OPENAI_API_KEY environment variables, Client configuration dictionaries, or the SecretStore for UI-driven scenarios.
  • Custom base_url values 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →