crademaker / openrouter-wrapper
OpenRouter API wrapper as a Composer-compatible PHP package
Requires
- php: ^8.1
- nyholm/psr7: ^1.8
- php-http/discovery: ^1.20
- psr/http-client: ^1.0
- psr/http-message: ^1.1 || ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.66
- guzzlehttp/guzzle: ^7.9
- php-http/guzzle7-adapter: ^1.1
- phpunit/phpunit: ^10.5
README
Composer-compatible PHP wrapper for the OpenRouter API, built on PSR-18.
Installation
composer require crademaker/openrouter-wrapper
Quick Start
<?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';
Usage In Existing Projects
Standard installation via Packagist:
composer require crademaker/openrouter-wrapper
Minimal 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
Language Policy
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.
Example READMEs
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
Typed DTO API
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)
API Coverage
Coverage is based on the OpenRouter OpenAPI specification retrieved on March 5, 2026.
Inference
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=...
Models And Providers
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
Credits
getCredits()->GET /creditscreateCoinbaseCharge(array $payload)->POST /credits/coinbase
API Keys And Auth
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
Guardrails
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
Streaming / Raw Responses
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();
Error Handling
ApiException: non-2xx API responseTransportException: network or PSR-18 transport failureInvalidResponseException: invalid JSON or JSON encoding failure
Development
composer validate
composer test
composer cs-check
Run live integration tests with a real API key (optional):
export OPENROUTER_API_KEY=...
composer test-integration
The 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
Lizenz
MIT