Sending Chat Requests to OpenRouter.ai with the Simplified OpenRouter PHP SDK

Learn how the Simplified OpenRouter PHP SDK lets you tap hundreds of AI models through one unified API. Install with Composer, send a chat request in a few lines, and switch models—GPT, Claude, and more—by changing a single string.

Shameless Plug: check out my new book, co-authored with the real-world knowledgeable author Rainier Sarabia: https://www.packtpub.com/en-us/product/php-programming-in-the-ai-era-9781835889930

Providing Context

In the processing of reviewing my (far too many!) subscriptions, I happened across openerouter.ai. Please bear in mind that I'm not paid by them, nor am I endorsing them. However, what drew me to this platform was due to the increasing restrictions of my current AI platforms. A phrase commonly used for what I've experienced it vendor lock-in. What I was specifically looking for was a good way to cut costs without sacrificing quality. In short, I wanted to gain access to the spectrum of new Chinese AI models.

But, to my horror, I noted that openrouter.ai lacks a PHP SDK. The nerve of them! Leaving out a programming language that's mature and stable, and runs on around 75% of the servers on the Internet! So, like any good PHP developer, hyped on AI-assistants, took the challenge to heard, and had Claude Code help me write an SDK. (Only to belatedly discover that one (well, several, actually), already existed on packagist.org. Ah well (mental shrug of the shoulders), at least the good news is that my SDK, introduced in this blog, is slim and trim, and doesn't try to do everything that the "official" (Python -- GAG) SDK does.

One Gateway, Every Model

If you build with large language models, you have probably felt the friction of provider lock-in. You wire your application to OpenAI, then a client asks for Anthropic's Claude, and later your team wants to experiment with a cheaper open-weight model from DeepSeek or Alibaba. Each vendor has its own SDK, its own authentication scheme, and its own quirks.

OpenRouter.ai solves this problem via its unified API gateway that sits in front of hundreds of models from dozens of providers. You authenticate once, you learn one request format, and you gain access to the entire catalog through a single endpoint. OpenRouter handles routing, fallbacks, and billing, so switching from one model to another becomes a matter of changing a single string in your request. For PHP developers, though, there has historically been a gap: OpenRouter's officially generated SDKs don't include PHP.

Enter the Simplified OpenRouter PHP SDK

That gap is what the simplified_openrouter_php_sdk by Doug Bierer (i.e. me -- but let us not forget Claude!) fills. It is an unofficial, hand-written, idiomatic PHP client modeled after OpenRouter's official Python SDK — but deliberately trimmed down. Where the Python SDK is auto-generated from OpenRouter's OpenAPI spec and covers roughly 90 endpoint groups (TTS/STT, video generation, OAuth, workspaces, datasets, guardrails, and more), this PHP SDK focuses on the subset most developers actually reach for:

  • Chat Completions: including streaming
  • Models: list, get, and count
  • Endpoints: see which providers serve a given model
  • Generations: usage and cost metadata lookup
  • Credits: account balance
  • API Keys: full CRUD (management key required)
  • Embeddings: foundation for Retrieval Augmented Generation (RAG)

The SDK leverages the Guzzle HTTP client and produces PSR-7 compliant requests and responses. Its minified architecture includes a single Transport class plus one resource class per endpoint group, making it easy to extend if you later need something more exotic.

One important caveat: at the time of writing, this is an Alpha release. It is excellent for learning, prototyping, and internal tooling. If you need something battle-hardened for production, the README itself points you toward the more mature eatzy/openrouter-php-sdk as an alternative.

Requirements and Installation

The SDK requires PHP 8.1+ and Composer. Installation is a one-liner:

composer require unlikelysource/simplified-openrouter-php-sdk

You will also need an OpenRouter API key, which you can generate from your OpenRouter dashboard. (The key looks somethihg like this: sk-or-....) The SDK will read it automatically from the OPENROUTER_API_KEY environment variable, or you can pass it explicitly when constructing the client. The latter is convenient for quick scripts, while the environment variable is safer for anything committed to a repository.

Sending Your First Chat Request

Here is the complete flow for a single text chat request. Notice how little ceremony is involved:


use OpenRouter\Client;
use OpenRouter\DTO\ChatMessage;

// Reads OPENROUTER_API_KEY from the environment if not passed explicitly.
$client = new Client(apiKey: 'sk-or-...');

$response = $client->chat->create([
    'model' => 'openai/gpt-4o-mini',
    'messages' => [
        ChatMessage::system('You are a helpful assistant.'),
        ChatMessage::user('Say hello in three languages.'),
    ],
]);

echo $response->getContent();

Three things are worth highlighting. First, $client->chat->create() accepts the request body as a plain associative array that maps directly to the OpenRouter Chat Completions API. Nothing is hidden behind a rigid data object, so any parameter the API supports — temperature, max_tokens, tools, tool_choice, provider, reasoning, response_format, and so on — can be passed straight through.

Second, the messages array can contain either plain arrays or the expressive ChatMessage helpers: ChatMessage::system(), ::user(), ::assistant(), and ::tool(). These make your intent readable at a glance.

Third, $response->getContent() gives you the model's reply as a string, so you can echo it, store it, or return it from an API endpoint immediately.

Switching Models

This is where OpenRouter and the SDK truly shine. To swap models, you change exactly one value — the model string. Everything else about your code stays identical:

// GPT-4o mini (fast, inexpensive)
$response = $client->chat->create([
    'model' => 'openai/gpt-4o-mini',
    'messages' => [ChatMessage::user('Summarize PHP 8.3 in one sentence.')],
]);

// Switch to Claude — same code, different model string
$response = $client->chat->create([
    'model' => 'anthropic/claude-3.5-sonnet',
    'messages' => [ChatMessage::user('Summarize PHP 8.3 in one sentence.')],
]);

Because the model identifier is just a string, you can drive it from configuration, a database, or a user-facing dropdown. If you are not sure which models are available, query using the SDK as follows:

$models = $client->models->list(['limit' => 20, 'category' => 'programming']);
foreach ($models as $model) {
    echo "{$model->id}: {$model->contextLength} tokens\n";
}

Streaming Responses

For chat UIs, you rarely want users staring at a spinner. The SDK supports token-by-token streaming with the same simple shape:


$stream = $client->chat->createStreamed([
    'model' => 'openai/gpt-4o-mini',
    'messages' => [ChatMessage::user('Count to 5.')],
]);

foreach ($stream as $chunk) {
    echo $chunk->getContentDelta();
}

createStreamed() returns a ChatCompletionStream that lazily parses OpenRouter's text/event-stream response and yields one chunk per server-sent event, stopping automatically at the [DONE] sentinel.

Robust by Default

The client is configurable for real-world conditions. You can set a custom baseUrl (for example, https://eu.openrouter.ai/api/v1 for EU in-region routing), pass an HTTP-Referer and X-Title for OpenRouter's app rankings, tune timeouts, and configure automatic retries via RetryConfig. Error handling is idiomatic too: failed requests throw a subclass of ApiException, such as UnauthorizedException, PaymentRequiredException, or TooManyRequestsException, allowing you to catch exactly what you need.

Wrapping Up

The Simplified OpenRouter PHP SDK delivers the 20% of OpenRouter's surface area that covers 80% of real work: chat, models, embeddings, and account management. Install it, drop in your API key, send a chat request, and switch models by editing one string. For prototyping and internal tools, it's a low-friction way to bring the entire OpenRouter model catalog into your PHP applications.


Here's the Amazon link to my new book, co-authored with the inscrutable and ever-knowledgeable author Rainier Sarabia: https://www.amazon.com/PHP-Programming-Era-applications-production-ready/dp/1835889921