# How to Handle Provider Authentication with API Keys in aisuite

> Learn to handle provider authentication with API keys in aisuite. Discover a three-step resolution chain: explicit config, env vars, and SecretStore for secure key management.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-07-30

---

**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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/client.py), the `configure` method merges new settings into the existing configuration:

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/openai_provider.py), [`platform/coworker/providers/anthropic_provider.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/anthropic_provider.py), and [`platform/coworker/providers/gemini_provider.py`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/openai_provider.py), the `resolve_api_key` function implements the chain:

```python
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`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/openai_provider.py), applies the resolution chain:

```python
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:

```bash
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:

```python
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:

```python
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`:

```python
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`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/openai_provider.py)**: Contains the `resolve_api_key` function and `_ensure_client` lazy initialization logic for OpenAI.
- **[`platform/coworker/providers/anthropic_provider.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/anthropic_provider.py)**: Implements the same resolution pattern for Anthropic with `ANTHROPIC_API_KEY`.
- **[`platform/coworker/providers/gemini_provider.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/gemini_provider.py)**: Handles Google Gemini authentication via `GEMINI_API_KEY` or `GOOGLE_API_KEY`.
- **[`platform/coworker/providers/registry.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/providers/registry.py)**: Provider factory that forwards `api_key` and other config values from the high-level client to individual providers.
- **[`aisuite/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/client.py)**: High-level client interface that stores and manages `provider_configs`.
- **[`platform/coworker/secrets.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/secrets.py)**: Implements the in-memory `SecretStore` used 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_key` in its respective file (e.g., [`platform/coworker/providers/openai_provider.py`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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.