cjmellor/fal-ai-laravel

A Laravel SDK for Fal.ai

Installs: 347

Dependents: 0

Suggesters: 0

Security: 0

Stars: 5

Watchers: 0

Forks: 1

Open Issues: 0

pkg:composer/cjmellor/fal-ai-laravel

v2.0.0 2026-01-12 18:50 UTC

README

Fal AI Laravel SDK Banner

Fal.ai Laravel Package

Latest Version on Packagist GitHub Tests Action Status Total Downloads Packagist PHP Version Laravel Version

A Laravel package for integrating with the Fal.ai API, providing a fluent interface for AI model execution with built-in webhook support, streaming, and Platform APIs.

Note

Multi-provider support: This package also includes a Replicate driver for Replicate.com.

Features

  • Fluent API for building model requests
  • Queue and Sync execution modes
  • Real-time streaming with Server-Sent Events (SSE)
  • Webhook support with ED25519 signature verification
  • Platform APIs for pricing, usage, analytics, and cost estimation
  • Multi-provider architecture

Warning

Upgrading from v1.x? Version 2.0 is a complete architectural rewrite with breaking changes. The configuration structure, API methods, and class namespaces have all changed. You must follow the Upgrade Guide to migrate from v1.x to v2.x.

Installation

Install via Composer:

composer require cjmellor/fal-ai-laravel

Publish the configuration:

php artisan vendor:publish --tag=fal-ai-config

Add your API key to .env:

FAL_API_KEY=your_fal_api_key

Basic Usage

use Cjmellor\FalAi\Facades\FalAi;

$response = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A beautiful sunset over mountains')
    ->imageSize('landscape_4_3')
    ->run();

$requestId = $response->requestId;

Using a Default Model

Set a default model in your config to omit the model ID:

// config/fal-ai.php
'drivers' => [
    'fal' => [
        'default_model' => 'fal-ai/flux/schnell',
    ],
],

// Usage
$response = FalAi::model()
    ->prompt('A cozy cabin in the woods')
    ->run();

Queue vs Sync Modes

Queue Mode (Default)

Requests are processed asynchronously. Use webhooks or polling to get results.

$response = FalAi::model('fal-ai/flux/dev')
    ->prompt('A detailed portrait')
    ->queue() // Optional - queue is the default
    ->run();

// Returns immediately with request_id
$requestId = $response->requestId;

Best for: Complex generations, batch processing, production workloads.

Sync Mode

Requests block until complete and return the result directly.

$response = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A quick sketch')
    ->sync()
    ->run();

// Returns the complete result
$images = $response->json()['images'];

Best for: Simple generations, interactive applications, development.

Warning

Sync mode may timeout for complex requests.

Polling Status & Results

For queued requests, poll for status and retrieve results:

// Check status
$status = FalAi::driver('fal')->status($requestId, 'fal-ai/flux/dev');

if ($status->json()['status'] === 'COMPLETED') {
    // Get the result
    $result = FalAi::driver('fal')->result($requestId, 'fal-ai/flux/dev');
    $images = $result->json()['images'];
}

// Cancel a queued request
FalAi::driver('fal')->cancel($requestId, 'fal-ai/flux/dev');

Response Helpers

$response = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A fox in a forest')
    ->run();

$response->requestId;    // Request ID
$response->statusUrl;    // URL to check status
$response->responseUrl;  // URL to get result
$response->cancelUrl;    // URL to cancel

Streaming

Stream responses in real-time using Server-Sent Events:

$stream = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A dancing robot')
    ->stream();

// Process the stream response
$stream->getResponse();

Note

Not all models support streaming. Check model documentation.

Webhook Support

Setting a Webhook URL

Adding a webhook automatically uses queue mode:

$response = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A beautiful landscape')
    ->withWebhook('https://yourapp.com/webhooks/fal')
    ->run();

Important

Webhook URLs must use HTTPS and be publicly accessible.

Built-in Webhook Route

The package provides a pre-configured route at /webhooks/fal:

$response = FalAi::model('fal-ai/flux/schnell')
    ->withWebhook(url('/webhooks/fal'))
    ->prompt('Your prompt')
    ->run();

Custom Webhook Endpoint

Create your own endpoint with the verification middleware:

use Cjmellor\FalAi\Middleware\VerifyFalWebhook;

Route::post('/webhooks/fal-custom', function (Request $request) {
    $payload = $request->json()->all();

    if ($payload['status'] === 'OK') {
        $images = $payload['data']['images'];
        // Process images
    }

    return response()->json(['status' => 'processed']);
})->middleware(VerifyFalWebhook::class);

Manual Verification

use Cjmellor\FalAi\Services\WebhookVerifier;
use Cjmellor\FalAi\Exceptions\WebhookVerificationException;

$verifier = new WebhookVerifier();

try {
    $verifier->verify($request);
    // Webhook is valid
} catch (WebhookVerificationException $e) {
    // Verification failed
}

Webhook Payload

Success:

{
    "request_id": "req_123",
    "status": "OK",
    "data": {
        "images": [{"url": "https://...", "width": 1024, "height": 768}],
        "seed": 12345
    }
}

Error:

{
    "request_id": "req_123",
    "status": "ERROR",
    "error": {"type": "ValidationError", "message": "Invalid prompt"}
}

Platform APIs

Access Fal.ai Platform APIs for pricing, usage, and analytics.

Pricing

$pricing = FalAi::platform()
    ->pricing()
    ->forEndpoints(['fal-ai/flux/dev', 'fal-ai/flux/schnell'])
    ->get();

$unitPrice = $pricing->getUnitPriceFor('fal-ai/flux/dev');

Cost Estimation

// Estimate by API calls
$estimate = FalAi::platform()
    ->estimateCost()
    ->historicalApiPrice()
    ->endpoint('fal-ai/flux/dev', callQuantity: 100)
    ->estimate();

echo $estimate->totalCost; // e.g., 2.50

// Estimate by billing units
$estimate = FalAi::platform()
    ->estimateCost()
    ->unitPrice()
    ->endpoint('fal-ai/flux/dev', unitQuantity: 100)
    ->estimate();

Usage

$usage = FalAi::platform()
    ->usage()
    ->forEndpoint('fal-ai/flux/dev')
    ->between('2025-01-01T00:00:00Z', '2025-01-31T23:59:59Z')
    ->timeframe('day')
    ->withSummary()
    ->get();

$totalCost = $usage->getTotalCost();
$totalQuantity = $usage->getTotalQuantity();

Analytics

$analytics = FalAi::platform()
    ->analytics()
    ->forEndpoint('fal-ai/flux/dev')
    ->between('2025-01-01', '2025-01-31')
    ->withAllMetrics()
    ->get();

$totalRequests = $analytics->getTotalRequests();
$successRate = $analytics->getSuccessRate();

Delete Request Payloads

Remove stored input/output data for a request:

$response = FalAi::platform()
    ->deleteRequestPayloads('req_123456789')
    ->delete();

if (!$response->hasErrors()) {
    echo "Deleted successfully";
}

Fluent API

Dynamic Methods

Method names are converted from camelCase to snake_case:

FalAi::model('fal-ai/flux/schnell')
    ->prompt('A sunset')           // prompt
    ->imageSize('1024x1024')       // image_size
    ->numInferenceSteps(50)        // num_inference_steps
    ->guidanceScale(7.5)           // guidance_scale
    ->negativePrompt('blurry')     // negative_prompt
    ->numImages(2)                 // num_images
    ->seed(12345)                  // seed
    ->run();

Bulk Data

$response = FalAi::model('fal-ai/flux/schnell')
    ->with([
        'prompt' => 'A landscape',
        'image_size' => '1024x1024',
        'num_images' => 2,
    ])
    ->run();

Immutable Methods

Create new instances without modifying the original:

$base = FalAi::model('fal-ai/flux/schnell')
    ->imageSize('1024x1024')
    ->numImages(1);

$request1 = $base->promptImmutable('A dragon');
$request2 = $base->promptImmutable('A unicorn');

// $base is unchanged

Conditional Methods

$response = FalAi::model('fal-ai/flux/schnell')
    ->prompt('A sunset')
    ->when($highQuality, fn($req) => $req->numInferenceSteps(100))
    ->unless($skipSeed, fn($req) => $req->seed(42))
    ->run();

Configuration

// config/fal-ai.php
return [
    'default' => env('AI_DRIVER', 'fal'),

    'drivers' => [
        'fal' => [
            'api_key' => env('FAL_API_KEY'),
            'base_url' => env('FAL_BASE_URL', 'https://queue.fal.run'),
            'sync_url' => env('FAL_SYNC_URL', 'https://fal.run'),
            'platform_base_url' => env('FAL_PLATFORM_URL', 'https://api.fal.ai'),
            'default_model' => env('FAL_DEFAULT_MODEL'),
            'webhook' => [
                'jwks_cache_ttl' => 86400,
                'timestamp_tolerance' => 300,
                'verification_timeout' => 10,
            ],
        ],
    ],
];

Error Handling

use Saloon\Exceptions\Request\RequestException;
use Cjmellor\FalAi\Exceptions\WebhookVerificationException;

try {
    $response = FalAi::model('fal-ai/flux/schnell')
        ->prompt('A sunset')
        ->run();
} catch (RequestException $e) {
    $status = $e->getResponse()->status();
    $body = $e->getResponse()->json();
} catch (WebhookVerificationException $e) {
    // Webhook verification failed
}

Replicate Driver

This package includes a driver for Replicate.com.

Setup

Add your Replicate API key to .env:

REPLICATE_API_KEY=your_replicate_api_key

Usage

use Cjmellor\FalAi\Facades\FalAi as Ai;

$response = Ai::driver('replicate')
    ->model('stability-ai/sdxl')
    ->prompt('A majestic dragon')
    ->numOutputs(2)
    ->run();

Model Format

Replicate models can use two formats:

Official Models:

// Format: owner/model
->model('stability-ai/sdxl')

Custom Models (with specific version):

// Format: owner/model:version
->model('your-username/my-custom-model:da77bc59ee60...')

Note

The :version suffix is only required for custom models. Official Replicate models use just owner/model.

Checking Status

Replicate uses polling for status:

$response = Ai::driver('replicate')
    ->model('stability-ai/sdxl')
    ->prompt('A landscape')
    ->run();

// Poll for completion
$status = Ai::driver('replicate')->status($response->id);

// Status helpers
$status->isRunning();    // starting or processing
$status->isSucceeded();  // completed successfully
$status->isFailed();     // failed
$status->isCanceled();   // canceled
$status->isTerminal();   // any final state

// Get result when complete
if ($status->isSucceeded()) {
    $output = $status->output;
}

Key Differences from Fal

Feature Fal.ai Replicate
Queue/Sync modes Yes No (always async)
Streaming Yes No (use polling)
Platform APIs Yes No
Webhooks Yes Yes

Replicate Webhooks

$response = Ai::driver('replicate')
    ->model('stability-ai/sdxl')
    ->prompt('A sunset')
    ->withWebhook('https://yourapp.com/webhooks/replicate')
    ->run();

Built-in route available at /webhooks/replicate.

Configure webhook verification in .env:

REPLICATE_WEBHOOK_SECRET=your_webhook_secret

Testing

composer test

Security

Important

Webhook Verification:

  • Fal.ai: ED25519 signatures with JWKS
  • Replicate: HMAC-SHA256 signatures

Always use HTTPS for webhook URLs and keep API keys secure.

Contributing

Contributions are welcome! Please submit a Pull Request.

License

MIT License. See LICENSE for details.