Composer-compatible PHP wrapper for the OpenRouter API, built on PSR-18.
composer require crademaker/openrouter-wrapper<?php
declare(strict_types=1);
use Crademaker\OpenRouter\OpenRouterClient;
use Crademaker\OpenRouter\OpenRouterConfig;
require __DIR__ . '/vendor/autoload.php';
$config = new OpenRouterConfig(
apiKey: getenv('OPENROUTER_API_KEY') ?: '',
appUrl: 'https://your-app.example',
appName: 'My Composer App',
);
$client = new OpenRouterClient($config);
$response = $client->chatCompletions([
'model' => 'openai/gpt-4o-mini',
'messages' => [
['role' => 'system', 'content' => 'You are a helpful assistant.'],
['role' => 'user', 'content' => 'Say hello in one sentence.'],
],
]);
echo $response['choices'][0]['message']['content'] ?? 'No response';Standard installation via Packagist:
composer require crademaker/openrouter-wrapperMinimal bootstrap in an existing project:
<?php
declare(strict_types=1);
use Crademaker\OpenRouter\OpenRouterClient;
use Crademaker\OpenRouter\OpenRouterConfig;
$client = new OpenRouterClient(
new OpenRouterConfig(
apiKey: $_ENV['OPENROUTER_API_KEY'] ?? '',
appUrl: 'https://your-app.example',
appName: 'Your Project Name',
),
);Typical follow-up:
- register the client as a service or singleton
- inject
OPENROUTER_API_KEYthrough.env, a secret store, or CI variables - use concrete model IDs instead of routing aliases if you need reproducible behavior
This repository follows a simple rule for public distribution:
- public package surface is written in English
- GitHub-facing documentation is written in English
- Composer metadata is written in English
- examples and release-facing documentation are written in English
This keeps the package aligned with common GitHub, Packagist, and Composer expectations.
Common use cases are documented under examples/:
- examples/README.md
- examples/chat/README.md
- examples/responses/README.md
- examples/embeddings/README.md
- examples/vision/README.md
- examples/streaming/README.md
In addition to array payloads, the package provides semantic DTOs per endpoint group under Crademaker\OpenRouter\DTO\....
<?php
declare(strict_types=1);
use Crademaker\OpenRouter\DTO\Request\InferenceRequest;
use Crademaker\OpenRouter\DTO\Request\ListModelsQuery;
use Crademaker\OpenRouter\DTO\Request\ChatMessage;
use Crademaker\OpenRouter\Enum\ChatRole;
use Crademaker\OpenRouter\Enum\ModelCategory;
use Crademaker\OpenRouter\Enum\SupportedParameter;
$inference = $client->chatCompletionsDto(
InferenceRequest::chat('openai/gpt-4o-mini', [
ChatMessage::withRole(ChatRole::USER, 'Hello'),
]),
);
echo $inference->firstText() ?? 'No response';
echo $inference->firstChoice()?->message()?->role() ?? '';
$models = $client->listModelsDto(ListModelsQuery::create(
category: ModelCategory::GENERAL,
supportedParameters: SupportedParameter::TOOLS,
));
echo (string) $models->count();
echo $models->modelObjects()[0]->id();Conventions:
- array-based methods remain available for backward compatibility
...Dto()methods return typed response objects- many array-based methods also accept request DTOs through union types
Examples of strict field objects:
- Request:
ChatMessage,InferenceRequest,EmbeddingsRequest,KeyRequest,GuardrailRequest - Response:
InferenceChoice,ModelInfo,KeyInfo,GuardrailInfo,ProviderInfo,EmbeddingVector - Enums:
ChatRole,ModelCategory,SupportedParameter(withenum|stringfallback in query DTOs)
Coverage is based on the OpenRouter OpenAPI specification retrieved on March 5, 2026.
chatCompletions(array $payload)->POST /chat/completionscompletions(array $payload)->POST /completions(Legacy-Alias)createMessages(array $payload)->POST /messagescreateResponses(array $payload)->POST /responsescreateEmbeddings(array $payload)->POST /embeddingslistEmbeddingsModels()->GET /embeddings/modelsgetGeneration(string $id)->GET /generation?id=...
listModels(array $query = [])->GET /modelslistModelsCount()->GET /models/countlistModelsUser()->GET /models/userlistModelEndpoints(string $author, string $slug)->GET /models/{author}/{slug}/endpointslistProviders()->GET /providerslistEndpointsZdr()->GET /endpoints/zdr
getCredits()->GET /creditscreateCoinbaseCharge(array $payload)->POST /credits/coinbase
getCurrentKey()->GET /keylistKeys(array $query = [])->GET /keysgetKey(string $hash)->GET /keys/{hash}createKeys(array $payload)/createKey(array $payload)->POST /keysupdateKeys(string $hash, array $payload)/updateKey(...)->PATCH /keys/{hash}deleteKeys(string $hash)/deleteKey(...)->DELETE /keys/{hash}exchangeAuthCodeForApiKey(array $payload)->POST /auth/keyscreateAuthorizationCode(array $payload)->POST /auth/keys/codegetUserActivity(?string $date = null)->GET /activity
listGuardrails(array $query = [])->GET /guardrailsgetGuardrail(string $id)->GET /guardrails/{id}createGuardrail(array $payload)->POST /guardrailsupdateGuardrail(string $id, array $payload)->PATCH /guardrails/{id}deleteGuardrail(string $id)->DELETE /guardrails/{id}listKeyAssignments(array $query = [])->GET /guardrails/assignments/keyslistMemberAssignments(array $query = [])->GET /guardrails/assignments/memberslistGuardrailKeyAssignments(string $id, array $query = [])->GET /guardrails/{id}/assignments/keyslistGuardrailMemberAssignments(string $id, array $query = [])->GET /guardrails/{id}/assignments/membersbulkAssignKeysToGuardrail(string $id, array $payload)->POST /guardrails/{id}/assignments/keysbulkUnassignKeysFromGuardrail(string $id, array $payload)->POST /guardrails/{id}/assignments/keys/removebulkAssignMembersToGuardrail(string $id, array $payload)->POST /guardrails/{id}/assignments/membersbulkUnassignMembersFromGuardrail(string $id, array $payload)->POST /guardrails/{id}/assignments/members/remove
Use requestRaw(...) for non-JSON responses such as SSE:
$response = $client->requestRaw('POST', '/responses', [
'model' => 'openai/gpt-4o-mini',
'input' => 'stream test',
'stream' => true,
]);
$rawSse = (string) $response->getBody();ApiException: non-2xx API responseTransportException: network or PSR-18 transport failureInvalidResponseException: invalid JSON or JSON encoding failure
composer validate
composer test
composer cs-checkRun live integration tests with a real API key (optional):
export OPENROUTER_API_KEY=...
composer test-integrationThe integration test suite currently verifies:
- live access to
getCurrentKey() - live access to
listModels() - live access to
getCredits() - live access to
listModelsCount() - live access to
listModelsUser() - live access to
listEmbeddingsModels() - a real embeddings request with
nvidia/llama-nemotron-embed-vl-1b-v2:free - live access to
listProviders() - live access to
listEndpointsZdr() - live access to
listModelEndpoints('stepfun', 'step-3.5-flash:free') - a real content query through
chat/completionswithstepfun/step-3.5-flash:free - a real content query through
responseswithstepfun/step-3.5-flash:free - a real content query through
messageswithstepfun/step-3.5-flash:free - a real streaming request through
requestRaw()against/responses - a real image-input query through
chat/completionswithgoogle/gemma-3-4b-it:free
Optional environment variables:
OPENROUTER_BASE_URL(default:https://openrouter.ai/api/v1)OPENROUTER_APP_URLOPENROUTER_APP_NAMEOPENROUTER_VISION_MODELOPENROUTER_TEST_IMAGE_URL
MIT