Custom API Keys in Laravel AI SDK
I've been working with Laravel's AI SDK recently, and I ran into a common problem: how do you handle custom API keys for different users or tenants? The SDK works great out of the box when you have a single API key in your configuration, but what about multi-tenant applications where each tenant (or user) brings their own API key (BYO key)?
This is a typical scenario in SaaS applications where you want to let users connect their own OpenAI (or compatible) accounts to your service. The Laravel AI SDK doesn't have a built-in mechanism for this, but it's surprisingly easy to solve with a small extension.
The Problem
When you look at the Laravel AI SDK, the Laravel Magicᵀᴹ creates drivers using configuration from your config/ai.php
file. For OpenAI-compatible drivers, it looks something like this:
// AiManager.php
public function createOpenaiCompatibleDriver(array $config): OpenAiCompatibleProvider
{
return new OpenAiCompatibleProvider(
$config,
$this->app->make(Dispatcher::class)
);
}
The issue is that this pulls the API key directly from your configuration, which is static and shared across all users/tenants. In a multi-tenant app, you need this key to be dynamic based on the current user or tenant.
The Solution
The fix involves three simple steps:
- Create a custom class that extends
AiManager::class. - Override the driver creation method to inject your custom API key or a user-selected model.
- Extend the
AIManager:classbinding in your service provider.
Here's exactly how to do it:
Step 1: Create the Custom Manager
First, create a custom AI manager that extends the base one:
// App/Ai/CustomAiManager.php
namespace App\Ai;
use Illuminate\Support\Arr;
use Laravel\AI\Managers\AiManager;
class CustomAiManager extends AiManager
{
public function createOpenaiCompatibleDriver(array $config): OpenAiCompatibleProvider
{
// Get the current tenant's API key however you want.
Arr::set($config, 'key', Auth::user()->api_key);
// Maybe the user has also selected a default model they want to use.
Arr::set($config, 'models.text.default', Auth::user()->default_text_model);
// Call the parent method with our modified config
return parent::createOpenaiCompatibleDriver($config);
}
}
Any of the configuration keys can be changed, depending on what the users need to do.
Step 2: Extend the Binding
Next, in your service provider (usually AppServiceProvider), extend the AIManager binding:
// App/Providers/AppServiceProvider.php
use App\Ai\CustomAiManager;
use Illuminate\Support\Facades\App;
use Laravel\AI\Managers\AiManager;
public function register(): void
{
$this->app->extend(AiManager::class, function (AiManager $manager, Application $app) {
return new CustomAiManager($app);
});
}
How It Works
What's happening here is clean and straightforward:
- We extend the base
AiManagerto create our own version. - We override just the method we need to customize (
createOpenaiCompatibleDriver). - In that method, we modify the
$configarray to use the current user's API key. - We call the parent method to do the actual driver creation with our modified config.
- Finally, we tell Laravel's service container to use our custom manager instead of the default one.
The key insight is that you only need to override the specific driver creation method you're using. If you're also using Anthropic or Gemini drivers, you'd override those methods similarly.
Why This Approach Works Well
This solution has several advantages:
- Minimal footprint - You're only adding a few lines of code.
- Leverages existing SDK - You're not reimplementing anything, just extending.
- Maintainable - Clear separation of concerns, easy to understand.
- Flexible - Easy to adapt to different authentication systems.
- No configuration changes - Works with your existing AI config as a fallback.
This pattern of extending Laravel's managers is powerful and can be applied to many other situations where you need to customize framework behavior based on the current user or tenant context. It's clean, follows Laravel's conventions, and keeps your solution tightly focused on the problem you're solving.