PHP code example of michaelfrank-dev / openrouter-php

1. Go to this page and download the library: Download michaelfrank-dev/openrouter-php library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

michaelfrank-dev / openrouter-php example snippets


use MichaelFrank\OpenRouter\Client\OpenRouterFactory;

// Automatically discovers PSR-18 and PSR-17 implementations
$client = OpenRouterFactory::create(
    apiKey: $_ENV['OPENROUTER_API_KEY'],
    siteUrl: 'https://myapp.com',
    siteName: 'My Application'
);

use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\CompletionOptions;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;
use MichaelFrank\OpenRouter\Requests\ProviderPreferences;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\ProviderSort;

$request = new CompletionRequest(
    model: '~openai/gpt-mini-latest',
    messages: [new UserMessage('Write a short poem about Rio de Janeiro.')],
    options: (new CompletionOptions())
        ->withProviderPreferences(
            new ProviderPreferences(sort: new ProviderSort(ProviderSort::PRICE))
        ),
);

$response = $client->completions($request);
echo $response->getFirstChoice()->message->content;

use MichaelFrank\OpenRouter\Requests\ProviderPreferences;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\PercentileCutoffs;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\ProviderSort;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\ProviderSortConfig;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\ProviderSortPartition;

$provider = new ProviderPreferences(
    order: ['anthropic', 'openai'],
    sort: new ProviderSortConfig(
        by: new ProviderSort(ProviderSort::THROUGHPUT),
        partition: new ProviderSortPartition(ProviderSortPartition::MODEL),
    ),
    preferredMinThroughput: new PercentileCutoffs(p50: 100.0),
    preferredMaxLatency: new PercentileCutoffs(p50: 0.5, p99: 2.0),
    maxPrice: ['prompt' => '0.55', 'completion' => '5.65'],
);

use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;

$request = new CompletionRequest(
    model: ['~openai/gpt-mini-latest', '~anthropic/claude-haiku-latest'],
    messages: [new UserMessage('How is it going?')],
);
$response = $client->completions($request);

use MichaelFrank\OpenRouter\Requests\ModelListQuery;
use MichaelFrank\OpenRouter\Requests\ModelListSort;

$models = $client->models(
    new ModelListQuery(
        sort: new ModelListSort(ModelListSort::THROUGHPUT_HIGH_TO_LOW),
        outputModalities: 'text',
    ),
);

use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\CompletionOptions;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;
use MichaelFrank\OpenRouter\Requests\Tools\OpenRouterWebSearchTool;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\SearchContextSize;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\WebSearchEngine;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\UserLocation;

$request = new CompletionRequest(
    model: '~anthropic/claude-haiku-latest',
    messages: [new UserMessage('Historical landmarks and tourist sites to visit in Recife')],
    options: new CompletionOptions(
        tools: [
            new OpenRouterWebSearchTool(
                engine: new WebSearchEngine(WebSearchEngine::EXA), // exa, auto, native, firecrawl, parallel, perplexity
                maxResults: 5,                                     // results per search call
                maxTotalResults: 15,                               // max cumulative results across searches
                searchContextSize: new SearchContextSize(SearchContextSize::MEDIUM), // low, medium, high
                maxCharacters: 2000,                               // exact max characters of content per result
                allowedDomains: ['wikipedia.org', 'reddit.com'],   // restrict search to these domains
                excludedDomains: ['pinterest.com'],                   // exclude these domains
                userLocation: new UserLocation(                    // geographic bias for search results (does not inject context into reasoning)
                    city: 'Belo Horizonte',
                    region: 'Minas Gerais',
                    country: 'Brazil',
                    timezone: 'America/Sao_Paulo'
                ),
                maxUses: 3,                                        // cap the number of searches model can perform
                xSearch: true                                      // enable X/Twitter search for SpaceXAI/Grok (or pass XSearchFilter)
            )
        ]
    )
);

$response = $client->completions($request);

use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\CompletionOptions;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;
use MichaelFrank\OpenRouter\Requests\Tools\OpenRouterWebFetchTool;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\WebFetchEngine;

$request = new CompletionRequest(
    model: '~openai/gpt-mini-latest',
    messages: [new UserMessage('Analyze the content on this webpage.')],
    options: new CompletionOptions(
        tools: [
            new OpenRouterWebFetchTool(
                engine: new WebFetchEngine(WebFetchEngine::AUTO),
                url: 'https://example.com',
                maxContentTokens: 1000,
                allowedDomains: ['example.com'],
                blockedDomains: ['irrelevant-site.com'],           // filter out domains not relevant to the search
                maxUses: 2
            )
        ]
    )
);

$response = $client->completions($request);

use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\CompletionOptions;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;
use MichaelFrank\OpenRouter\Requests\Tools\OpenRouterImageGenerationTool;
use MichaelFrank\OpenRouter\Enums\ImageQuality;
use MichaelFrank\OpenRouter\Enums\ImageBackground;
use MichaelFrank\OpenRouter\Enums\ImageOutputFormat;

$request = new CompletionRequest(
    model: 'openai/gpt-5.2',
    messages: [new UserMessage('Create an image of a futuristic city at sunset')],
    options: new CompletionOptions(
        tools: [
            new OpenRouterImageGenerationTool(
                model: 'openai/gpt-image-2', // which image generation model to use
                quality: ImageQuality::High,  // Auto, Low, Medium, High
                aspectRatio: '16:9',
                size: '1024x1024',
                background: ImageBackground::Transparent, // Auto, Transparent, Opaque
                outputFormat: ImageOutputFormat::Png, // Png, Jpeg, Webp, Svg
                outputCompression: 85,
                moderation: 'auto'
            )
        ]
    )
);

$response = $client->completions($request);

use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\CompletionOptions;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\SearchContextSize;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\WebSearchOptions;

$request = new CompletionRequest(
    model: '~deepseek/deepseek-v4-flash-latest',
    messages: [new UserMessage('What are the latest developments in quantum computing?')],
    options: (new CompletionOptions())
        ->withWebSearchOptions(
            new WebSearchOptions(new SearchContextSize(SearchContextSize::LOW))
        )
);

$response = $client->completions($request);

use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\CompletionOptions;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;
use MichaelFrank\OpenRouter\Requests\ProviderRouting\XSearchFilter;
use MichaelFrank\OpenRouter\Requests\Tools\OpenRouterWebSearchTool;

// With search filters:
$request = new CompletionRequest(
    model: '~x-ai/grok-latest',
    messages: [new UserMessage('What did OpenRouter announce this year?')],
    options: new CompletionOptions(
        tools: [
            new OpenRouterWebSearchTool(
                xSearch: new XSearchFilter(
                    allowedXHandles: ['OpenRouterAI'],
                    fromDate: '2026-01-01',
                    toDate: '2026-06-01',
                    enableImageUnderstanding: true,
                    enableVideoUnderstanding: false
                )
            )
        ]
    )
);

$response = $client->completions($request);

// Enable X Search with no filters:
$options = new CompletionOptions(
    tools: [
        new OpenRouterWebSearchTool(
            xSearch: true // or: XSearchFilter::enabled()
        )
    ]
);

use MichaelFrank\OpenRouter\Requests\Tools\SpaceXXSearchTool;

// Direct SpaceXAI { "type": "x_search" } tool:
$options = new CompletionOptions(
    tools: [
        new SpaceXXSearchTool()
    ]
);

use GuzzleHttp\Client as GuzzleClient;
use MichaelFrank\OpenRouter\Client\OpenRouterFactory;
use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;

// Make sure to configure Guzzle with stream => true to disable response buffering
$httpClient = new GuzzleClient(['stream' => true]);

$client = OpenRouterFactory::create(
    apiKey: $_ENV['OPENROUTER_API_KEY'],
    httpClient: $httpClient
);

$request = new CompletionRequest(
    model: 'meta-llama/llama-3-8b-instruct',
    messages: [new UserMessage('Write a poem about the Saci-Pererê, a mischievous Brazilian folklore character.')]
);

$chunks = $client->streamCompletions($request);

foreach ($chunks as $chunk) {
    echo $chunk->choices[0]->delta->content ?? '';
}

use MichaelFrank\OpenRouter\Requests\EmbeddingRequest;

$request = new EmbeddingRequest(
    model: 'text-embedding-3-small',
    input: 'Feijoada is one of the most traditional dishes in Brazilian cuisine.'
);

$response = $client->embeddings($request);

foreach ($response->data as $item) {
    // $item->embedding is an array of floats
    print_r($item->embedding);
}

use MichaelFrank\OpenRouter\Requests\RerankRequest;

$request = new RerankRequest(
    model: 'cohere/rerank-english-v3.0',
    query: 'What is the capital of Brazil?',
    documents: [
        'Brasilia is the capital of Brazil.',
        'Rio de Janeiro was the capital of Brazil until 1960 and remains a major cultural and touristic center.',
        'Sao Paulo is the largest city in Brazil by population and a major economic hub.'
    ],
    topN: 2
);

$response = $client->rerank($request);

foreach ($response->results as $result) {
    echo "Index: {$result->index}, Relevance Score: {$result->relevanceScore}\n";
}

use MichaelFrank\OpenRouter\Requests\AudioSpeechRequest;

$request = new AudioSpeechRequest(
    model: 'openai/tts-1',
    input: 'The Iguaçu Falls roar with the power of millions of liters of water, creating one of the most breathtaking and unforgettable landscapes on the planet.',
    voice: 'alloy',
    responseFormat: 'mp3'
);

$response = $client->speech($request);

// Save the audio stream contents directly to a file
$response->saveToFile(__DIR__ . '/output.mp3');

use MichaelFrank\OpenRouter\Requests\AudioTranscriptionRequest;

$request = AudioTranscriptionRequest::fromFile(
    path: __DIR__ . '/audio_file.mp3',
    model: 'hexgrad/kokoro-82m'
);

$response = $client->transcriptions($request);
echo $response->text;

use MichaelFrank\OpenRouter\Requests\ImageGenerationRequest;
use MichaelFrank\OpenRouter\Requests\ImageGenerationProviderPreferences;
use MichaelFrank\OpenRouter\Requests\ImageResolution;
use MichaelFrank\OpenRouter\Enums\ImageOutputFormat;
use MichaelFrank\OpenRouter\Enums\ImageBackground;

$request = new ImageGenerationRequest(
    model: 'krea/krea-2-medium-turbo',
    prompt: 'A sleek retro-futuristic hovercar parked on a neon-lit street in Tokyo, cyberpunk style, hyper-detailed',
    aspectRatio: '16:9',
    outputFormat: ImageOutputFormat::Png,
    resolution: new ImageResolution('512x512'), // Supports constants (e.g. ImageResolution::TWO_K) or custom strings (e.g. '1K', '512x512')
    background: ImageBackground::Auto,
    provider: new ImageGenerationProviderPreferences(
        allowFallbacks: false,
        only: ['google-ai-studio']
    )
);

// Standard image generation
$response = $client->images($request);

foreach ($response->data as $item) {
    // $item->b64Json is base64 encoded image bytes string
    $imageBytes = base64_decode($item->b64Json);
    file_put_contents(__DIR__ . '/hovercar.png', $imageBytes);
}

// Streaming image generation (SSE events)
$streamRequest = $request->withStream(true);
$chunks = $client->streamImages($streamRequest);

foreach ($chunks as $chunk) {
    // Each chunk contains one of: ImageGenPartialImageEvent, ImageGenTextChunkEvent, ImageGenCompletedEvent
    $event = $chunk->data;
    if ($event->getType() === 'image_generation.completed') {
        echo "Image generation complete!\n";
    }
}

use MichaelFrank\OpenRouter\Requests\CompletionOptions;
use MichaelFrank\OpenRouter\Requests\CompletionRequest;
use MichaelFrank\OpenRouter\Requests\Messages\AssistantMessage;
use MichaelFrank\OpenRouter\Requests\Messages\AssistantMessageToolCall;
use MichaelFrank\OpenRouter\Requests\Messages\ToolMessage;
use MichaelFrank\OpenRouter\Requests\Messages\UserMessage;
use MichaelFrank\OpenRouter\Requests\Tools\ToolDefinition;

// Define the tool schema
$weatherTool = new ToolDefinition(
    name: 'get_current_weather',
    description: 'Get the current weather for a specified location',
    parameters: [
        'type' => 'object',
        'properties' => [
            'location' => [
                'type' => 'string',
                'description' => 'The city to search for'
            ],
            'unit' => [
                'type' => 'string',
                'enum' => ['celsius', 'fahrenheit']
            ]
        ],
        ' [
                    'name' => $toolCall->function->name,
                    'arguments' => $toolCall->function->arguments
                ]
            );

            $followUpRequest = new CompletionRequest(
                model: '~openai/gpt-mini-latest',
                messages: [
                    new UserMessage('What is the weather like in Florianópolis?'),
                    new AssistantMessage(toolCalls: [$assistantToolCall]),
                    new ToolMessage(content: $weatherResult, toolCallId: $toolCall->id)
                ]
            );

            $finalResponse = $client->completions($followUpRequest);
            echo $finalResponse->getFirstChoice()->message->content;
        }
    }
}

$response = $client->completions($request);

$metadata = $response->metadata;
echo "Request ID: " . $metadata->requestId . "\n";

// Access token consumption and cost from the response usage DTO
if ($response->usage !== null) {
    echo "Prompt Tokens: " . $response->usage->promptTokens . "\n";
    echo "Completion Tokens: " . $response->usage->completionTokens . "\n";
    echo "Request Cost: $" . ($response->usage->cost ?? '0.000000') . "\n";
}

$rateLimit = $metadata->rateLimit;
echo "Limit: " . $rateLimit->limit . "\n";
echo "Remaining: " . $rateLimit->remaining . "\n";

if ($rateLimit->resetAt !== null) {
    echo "Resets at: " . $rateLimit->resetAt->format(\DateTimeInterface::ATOM) . "\n";
    echo "Seconds remaining: " . $rateLimit->getRetryAfterSeconds() . "\n";
}

// Check user credit details
$credits = $client->credits();
echo "Credits used: " . $credits->creditsUsed . "\n";
echo "Credits remaining: " . ($credits->creditsRemaining ?? 'Unknown') . "\n";

// Get stats for a completed generation by its ID
$response = $client->generation(id: 'some-id');
$stats = $response->data[0] ?? null;

if ($stats !== null) {
    echo "Model: " . $stats->model . "\n";
    echo "Provider: " . ($stats->provider ?? 'Unknown') . "\n";
    echo "Price: $" . $stats->price . "\n";
    echo "Standard Prompt Tokens: " . $stats->tokensPrompt . "\n";
    echo "Standard Completion Tokens: " . $stats->tokensCompletion . "\n";
    echo "Native Prompt Tokens: " . $stats->nativeTokensPrompt . "\n";
    echo "Native Completion Tokens: " . $stats->nativeTokensCompletion . "\n";
}

// List all available image generation models
$modelsResponse = $client->imageModels();

foreach ($modelsResponse->data as $model) {
    echo "Model ID: " . $model->id . "\n";
    echo "Display Name: " . $model->name . "\n";
    echo "Description: " . $model->description . "\n";
    echo "Input Modalities: " . implode(', ', $model->architecture->inputModalities) . "\n";
    echo "Output Modalities: " . implode(', ', $model->architecture->outputModalities) . "\n";
    
    // Check supported parameters (resolution, seed, output_compression, etc.)
    foreach ($model->supportedParameters as $param => $capability) {
        echo " - Parameter: {$param} (Type: {$capability->type})\n";
        if ($capability->type === 'enum' && $capability->values !== null) {
            echo "   Allowed values: " . implode(', ', $capability->values) . "\n";
        } elseif ($capability->type === 'range') {
            echo "   Numeric range: {$capability->min} to {$capability->max}\n";
        }
    }
}

// Retrieve detailed per-endpoint capabilities & pricing for a specific model
$endpointsResponse = $client->imageModelEndpoints(
    author: 'bytedance-seed',
    slug: 'seedream-4.5'
);

echo "Model: " . $endpointsResponse->id . "\n";
foreach ($endpointsResponse->endpoints as $endpoint) {
    echo "Provider: " . $endpoint->providerName . " ({$endpoint->providerSlug})\n";
    echo "Supports streaming: " . ($endpoint->supportsStreaming ? 'Yes' : 'No') . "\n";
    
    // Detailed pricing entries per dimension
    foreach ($endpoint->pricing as $price) {
        echo " - Pricing: {$price->billable} cost: \${$price->costUsd} per {$price->unit}\n";
    }
}

use MichaelFrank\OpenRouter\Exceptions\OpenRouterException;
use MichaelFrank\OpenRouter\Exceptions\ApiRequestException;
use MichaelFrank\OpenRouter\Exceptions\NetworkException;

try {
    $response = $client->completions($request);
} catch (ApiRequestException $e) {
    // Inspect HTTP status code (e.g., 429, 401, 500)
    echo "API Error Status: " . $e->getStatusCode() . "\n";
    echo "Error Details: " . $e->getMessage() . "\n";
    echo "Raw Response Body: " . $e->getResponseBody() . "\n";

    // Extract rate limits or generation headers if present
    if ($e->getMetadata() !== null) {
        $rateLimit = $e->getMetadata()->rateLimit;
        echo "Remaining requests: " . $rateLimit->remaining . "\n";
        if ($rateLimit->resetAt !== null) {
            echo "Retry after: " . $rateLimit->getRetryAfterSeconds() . " seconds\n";
        }
    }
} catch (NetworkException $e) {
    // Handle transient network issues
    echo "Network communication failure: " . $e->getMessage() . "\n";
} catch (OpenRouterException $e) {
    // Fallback for general SDK errors (validation, config, etc.)
    echo "SDK Exception: " . $e->getMessage() . "\n";
}
bash
composer