Skip to content
LaravelBlog

laravel/ai: Use OpenRouter images endpoint for image generation

LaravelBlogBot
LaravelBlogBot

Contributed by pushpak1300

Part of the ai v1.2.1 release

Image generation through the OpenRouter gateway now targets OpenRouter's dedicated /images endpoint instead of chat/completions (PR #1116, fixes #1114). The previous implementation packaged every image request as a chat completion — a messages array plus modalities: ['image'] — which only chat models capable of emitting images could answer. Image-only families such as Flux responded with a 404. This release routes image work to the endpoint OpenRouter built for it, and it lands as fully backward compatible for anyone upgrading from v1.2.0.

What changed under the hood

The OpenRouter gateway's generateImage() method now POSTs to images with a flat payload: model, prompt, an optional aspect_ratio, an optional resolution, and input_references when source images are attached. The nested image_config object, the messages array, and the buildImageMessages() helper that produced it are all gone.

Response parsing was updated to match the new contract. Generated images are read from data[].b64_json with a media_type field — defaulting to image/png when a provider omits it — instead of being extracted from data URIs embedded in a chat message. Token usage still comes from usage.prompt_tokens and usage.completion_tokens, an empty data array still yields an empty images collection, and meta->model now reports the model identifier you requested, since the images endpoint doesn't echo it back.

Why it matters

Models like black-forest-labs/flux-3-image, bytedance-seed/seedream-4.5, and x-ai/grok-imagine-image-2.0 are image-only: they don't participate in chat/completions at all, so generating with them through OpenRouter previously failed outright. With the dedicated endpoint, every image model family OpenRouter routes to is reachable through the same Image API, so you can pick models on capability rather than on which transport they happen to support. It also removes a Gemini-family assumption that was baked into the old option names and docblocks.

Your application code doesn't change

Nothing about the public API moves. Sizes, quality tiers, and attachments behave exactly as before:

use Laravel\Ai\Image;

$response = Image::of('A donut sitting on a kitchen counter.')
    ->landscape()
    ->quality('high')
    ->generate(provider: 'openrouter', model: 'black-forest-labs/flux-3-image');

foreach ($response->images as $image) {
    Storage::disk('public')->put('donut.png', base64_decode($image->image));
}

landscape() still maps to aspect_ratio: '3:2', and quality('high') still resolves to the 4K tier — it's just transmitted as resolution now. Editing from a reference image works too, with attachments sent as input_references instead of chat content parts:

$response = Image::of('Turn this into a watercolor painting.')
    ->attachments([new LocalImage(resource_path('images/red.png'))])
    ->generate(provider: 'openrouter', model: 'google/gemini-3.1-flash-image');

Provider options: same input, clearer precedence

If you pass OpenRouter-specific options via withProviderOptions(), the image_config key is still accepted. The gateway pulls it out and forwards its aspect_ratio and image_size values as the equivalent top-level aspect_ratio and resolution fields:

Image::of('A neon skyline at dusk')
    ->withProviderOptions([
        'image_config' => ['aspect_ratio' => '21:9', 'image_size' => '2K'],
    ])
    ->generate(provider: 'openrouter', model: 'bytedance-seed/seedream-4.5');

One nuance worth knowing: when both are set, the fluent methods win. In the test suite, landscape() plus quality('high') alongside an image_config of '21:9' / '1K' produced aspect_ratio: '3:2' and resolution: '4K' on the wire.

Upgrade impact and verification status

For most applications there is nothing to do beyond pulling the release. Two edge cases deserve a quick look:

  • If you have custom tests, proxies, or middleware asserting on the raw OpenRouter image request or response, update them: requests go to https://openrouter.ai/api/v1/images, and responses use data[].b64_json and media_type.
  • If you rely on $response->meta->model matching a provider-echoed model string, note that it now reports the model you passed in.

Live coverage so far: basic and square generation pass for Gemini, GPT Image, Flux, Seedream, and Grok models through OpenRouter. The reference-image + resolution + landscape combination is wired into the integration suite but hasn't been run against the live API yet — worth a quick staging smoke test if image editing is on your critical path.

Takeaways

  • OpenRouter image generation now uses the /images endpoint, fixing 404s for image-only models like Flux.
  • The public Image API is unchanged; upgrading from v1.2.0 requires no code changes.
  • quality() still maps to 1K/2K/4K, now sent as resolution; image_config provider options are flattened to top-level fields.
  • Fluent size/quality methods take precedence over image_config when both are set.
  • Verified live for basic and square generation across five model families; the reference-image landscape/resolution test is still pending.

Sources

More from this release

Related Articles