# API Dependency Injection Mechanism in Free-Claude-Code: FastAPI Provider Architecture Explained

> Discover the API dependency injection in Free Claude Code FastAPI. Learn how provider architecture injects runtime config and LLM instances for seamless integration.

- Repository: [Ali Khokhar/free-claude-code](https://github.com/Alishahryar1/free-claude-code)
- Tags: architecture
- Published: 2026-04-24

---

**Free-Claude-Code leverages FastAPI's `Depends()` system to inject runtime configuration, LLM provider instances, and optional Anthropic-style API key authentication via pure functions centralized in [`api/dependencies.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/api/dependencies.py).**
 
This article examines how the Alishahryar1/free-claude-code repository implements a clean, testable dependency injection (DI) layer that lazily constructs service objects and caches them across requests. By routing all provider instantiation through FastAPI's native DI framework, the codebase remains environment-driven and avoids hard-coded service dependencies.
 

## Core Dependency Functions in api/dependencies.py

 
The DI layer is defined in [`api/dependencies.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/api/dependencies.py), which exposes several callable dependencies that FastAPI resolves automatically per request or caches globally.
 

### Configuration Injection

 
The foundation of the system is `get_settings()`, a cached dependency that returns a Pydantic `Settings` instance. This object loads values from environment variables and `.env` files, making configuration available throughout the dependency chain.
 

```python
def get_settings() -> Settings:
    """Get application settings via dependency injection."""
    return _get_settings()

```

 
*Source: [`api/dependencies.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/api/dependencies.py#L21-L24)*
 

### Provider Factory and Caching

 
Provider instantiation follows a lazy-loading factory pattern. The private function `_create_provider_for_type()` (L32-L133) constructs specific `BaseProvider` subclasses—such as `NvidiaNimProvider`, `OpenRouterProvider`, or `LMStudioProvider`—based on the `provider_type` parameter. It validates API keys and extracts proxy URLs via the helper `_get_proxy_value()` (L26-L30), which falls back to empty strings for invalid values.
 
The public interface `get_provider_for_type()` (L36-L52) implements singleton caching:
 

```python
def get_provider_for_type(provider_type: str) -> BaseProvider:
    if provider_type not in _providers:
        try:
            _providers[provider_type] = _create_provider_for_type(provider_type, get_settings())
        except AuthenticationError as e:
            raise HTTPException(status_code=503, detail=get_user_facing_error_message(e)) from e
    logger.info("Provider initialized: {}", provider_type)
    return _providers[provider_type]

```

 
Instances are stored in the module-level `_providers` dictionary, ensuring that expensive HTTP client setup occurs only once. The convenience wrapper `get_provider()` (L88-L94) selects the default provider based on the `Settings.provider_type` property derived from the `MODEL` environment variable.
 

### Authentication Guard

 
Security is enforced through `require_api_key()` (L54-L86), an optional dependency that validates Anthropic-style API keys. When `Settings.anthropic_auth_token` is configured, this function inspects `x-api-key`, `Authorization: Bearer`, or `anthropic-auth-token` headers and compares them against the stored token. If validation fails, FastAPI returns a 401/403 response before the route handler executes. When the token is empty, the check becomes a no-op.
 

## Implementing Dependencies in Route Handlers

 
FastAPI consumes these dependencies through the `Depends()` callable. Route handlers in [`api/app.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/api/app.py) declare their requirements as parameters, allowing the framework to inject live objects and execute validation logic automatically.
 

```python
from fastapi import APIRouter, Depends
from api.dependencies import get_provider, require_api_key

router = APIRouter()

@router.post("/v1/chat/completions")
async def chat_completion(payload: ChatRequest,
                         provider = Depends(get_provider),
                         _: None = Depends(require_api_key)
):
    return await provider.chat(payload)

```

 
`Depends(get_provider)` injects the cached default provider instance, while `Depends(require_api_key)` acts as a gatekeeper. Because `_providers` caches instances at the module level, concurrent requests share the same initialized provider without reinitialization overhead.
 

## Provider Lifecycle and Cleanup

 
The module-level `_providers` dictionary maintains singleton provider instances throughout the application lifetime. When the server shuts down, `cleanup_provider()` (L96-L102) gracefully terminates all active connections:
 

```python
async def cleanup_provider():
    global _providers
    for provider in _providers.values():
        await provider.cleanup()
    _providers = {}

```

 
This async cleanup is registered in [`api/app.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/api/app.py) as a shutdown event handler, ensuring that HTTP sessions to external LLM services (NVIDIA NIM, OpenRouter, etc.) are properly closed.
 

## Testing with Dependency Overrides

 
The DI architecture facilitates unit testing by allowing runtime dependency overrides. Developers can swap real providers for mocks without modifying route implementations.
 

```python
from fastapi.testclient import TestClient
from api.app import app
from api.dependencies import get_provider

def fake_provider():
    class Dummy:
        async def chat(self, payload):
            return {"choices": [{"message": {"content": "mocked"}}]}
    return Dummy()

app.dependency_overrides[get_provider] = fake_provider

client = TestClient(app)
resp = client.post("/v1/chat/completions", json={"model": "any", "messages": []})
assert resp.json()["choices"][0]["message"]["content"] == "mocked"

```

 
By overriding `get_provider` on the FastAPI application instance, tests bypass network calls and the `require_api_key` validation while exercising the full route logic.
 

## Summary

 
- **FastAPI Depends()** provides the backbone for injecting `Settings`, provider instances, and authentication guards into request handlers.
- Provider objects are **lazy-loaded** via `_create_provider_for_type()` and **cached** in the module-level `_providers` dict, minimizing HTTP client initialization overhead.
- **Optional authentication** is centralized in `require_api_key()`, which inspects multiple header formats and fails fast before business logic executes.
- All configuration flows through a single **Pydantic `BaseSettings`** object defined in [`config/settings.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/config/settings.py), supporting `.env` files and environment variables.
- The design supports **complete testability** through FastAPI's dependency override mechanism, allowing mock providers without code changes.
 

## Frequently Asked Questions

 

### How does free-claude-code cache LLM provider instances across requests?

The `get_provider_for_type()` function in [`api/dependencies.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/api/dependencies.py#L36-L52) stores constructed provider objects in a module-level `_providers` dictionary. When a route requests a provider, FastAPI returns the cached instance if one exists; otherwise, it triggers `_create_provider_for_type()` to build and store a new object. This ensures that TLS configuration, API key headers, and HTTP session setup occur only once per provider type, even under concurrent load.
 

### Where is the API key validation logic implemented?

Authentication is handled by `require_api_key()` in [`api/dependencies.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/api/dependencies.py#L54-L86). This dependency inspects incoming requests for `x-api-key`, `Authorization: Bearer`, or `anthropic-auth-token` headers and validates them against `Settings.anthropic_auth_token`. If the setting is configured and validation fails, FastAPI raises an HTTP exception before the route handler executes, providing centralized security without cluttering business logic.
 

### Can specific routes use a different LLM provider than the default?

Yes. While `get_provider()` returns the system default based on the `MODEL` environment variable, you can inject a specific provider by wrapping `get_provider_for_type()` with a lambda: `Depends(lambda: get_provider_for_type("llamacpp"))`. This targets the LlamaCpp provider regardless of the default configuration, useful for health checks or model-specific endpoints.
 

### How are application settings loaded and propagated through the dependency chain?

The `get_settings()` function returns a Pydantic `Settings` instance from [`config/settings.py`](https://github.com/Alishahryar1/free-claude-code/blob/main/config/settings.py) that automatically loads environment variables, `.env` files in the project root or `~/.config/free-claude-code/.env`, and files pointed to by `FCC_ENV_FILE`. This singleton configuration object is passed implicitly to provider factory functions, ensuring that API keys, proxy URLs, and timeout values remain consistent across the dependency injection lifecycle.