Prism PHP with a Custom OpenAI-Compatible Endpoint

In short: The url key defaults to the OpenAI host with the version segment already included, which is the opposite convention from Spring AI where the base URL carries no version. Providers are addressed by enum rather than by string. Laravel caches configuration, so an env edit that appears to do nothing is a cached config rather than a wrong URL. The request timeout defaults to 30 seconds, which a routed gateway can exceed.

Prism is the Laravel package for talking to model providers: you publish a config file, put credentials in .env, and call a fluent builder. Pointing it at a gateway is one config key, and the first thing to know is what that key already contains.

providers.openai.url defaults to https://api.openai.com/v1 — the version path is part of the default. That is the opposite convention from Spring AI, where base-url carries no version and a separate completions-path supplies it. Paste a versioned URL into Prism and it is correct; paste an unversioned one and every request 404s.

You need a key before the code below runs. Create an account, generate a key, and copy the base URL (https://aicomp.ai/v1). Create one free →
Check current rates → Free to sign up · $1 minimum top-up · No prepayment

This page covers the config file, the two ways to override a URL per request, and the Laravel behaviour that makes an edit look like it did nothing.

The config file

php artisan vendor:publish --tag=prism-config
// config/prism.php
'providers' => [
    'openai' => [
        'url'     => env('OPENAI_URL', 'https://api.openai.com/v1'),
        'api_key' => env('OPENAI_API_KEY', ''),
    ],
],
OPENAI_URL=https://gw.example.com/v1
OPENAI_API_KEY=sk-your-gateway-key

Note what the fallback tells you: the default ends in /v1, so the value you put in OPENAI_URL is expected to end in /v1 too. The provider builds the operation path onto it, so a value of https://gw.example.com produces a request to /chat/completions where your gateway is listening on /v1/chat/completions.

Providers are selected by enum, not by string:

use Prism\Prism\Prism;
use Prism\Prism\Enums\Provider;

$response = Prism::text()
    ->using(Provider::OpenAI, 'gpt-5.6-terra')
    ->withPrompt('Explain quantum computing in two sentences.')
    ->asText();

Overriding per request, two ways that differ

// third argument to using()
Prism::text()
    ->using(Provider::OpenAI, 'gpt-5.6-terra', ['url' => 'https://gw.example.com/v1'])
    ->withPrompt('...')
    ->asText();
// or the dedicated method
Prism::text()
    ->using(Provider::OpenAI, 'gpt-5.6-terra')
    ->usingProviderConfig(['url' => 'https://gw.example.com/v1'])
    ->withPrompt('...')
    ->asText();

Both change the URL for that call. The difference is in what happens afterwards: usingProviderConfig() re-resolves the provider, so if you are switching endpoints per tenant or per request, that is the one that reliably rebuilds the client. The inline third argument is fine for a one-off on a provider that is already resolved.

The config cache hides your edit

This is the Laravel-specific trap, and it costs more time than the URL does.

Prism reads config/prism.php, and that file reads env(). In a deployed Laravel app the config is usually cached:

php artisan config:cache

Once cached, editing .env changes nothing — env() returns the cached value, and in some configurations returns null outright outside of config files. You change OPENAI_URL, reload, and requests still go to the old host, with no error that mentions caching.

php artisan config:clear     # development
php artisan config:cache     # re-build after changing .env in production

If the endpoint you configured is not the one receiving requests, clear the config cache before you touch the URL again.

The 30-second timeout is shorter than a gateway round trip

request_timeout defaults to 30 seconds and applies to every provider request unless you change it:

'request_timeout' => env('PRISM_REQUEST_TIMEOUT', 30),

A gateway that routes onward, or a large-context generation, can exceed that. The symptom is a client-side timeout rather than an API error — no status code from your gateway, nothing in its logs. Override globally with the config key, or per call:

Prism::text()
    ->using(Provider::OpenAI, 'gpt-5.6-terra')
    ->withClientOptions(['timeout' => 120])
    ->withPrompt('...')
    ->asText();

What server-side generation costs

Prism is a server-side library, so the shape here is a request-and-response rather than an agent loop: prompt in, completion out, with the tool loop only running when you attach tools. Input is what you send plus any conversation history you maintain yourself in your application.

A day of work on this page's workload is 200k input and 35k output tokens — 4M input and 0.7M output across 20 working days. That is mid-range for the tool matrix: larger than a chat client, smaller than a coding agent that re-reads a repository each step. See output vs input pricing for how the ratio picks the cheapest model.

Per 1M input / output tokens and a 20-day month
ModelGateway rate
in / out per 1M tokens
One day20 days
gpt-5.6-terra$1.00 / $6.00$0.41$8.20
claude-sonnet-5$1.00 / $5.00$0.38$7.50
deepseek-v4-flash$0.22 / $0.66$0.07$1.34
gemini-3.7-flash$0.375 / $1.875$0.14$2.81
grok-4.1$1.00 / $5.00$0.38$7.50

One day = 200k input + 35k output on this page's workload. 20 days = 4M input and 0.7M output. Rates checked 2026-10-11 — gateway rates move with upstream promotions, so verify the current number in your dashboard before committing to a budget.

Across 219 catalogue models that publish both rates, the median output rate is 4.0× the input rate, and 68% of them (150 models) charge at least 4× more for output than input. The other 624 raw catalogue entries are dropped by cleaning — test entries, dated snapshots and non-chat variants — not for a missing rate. This is why an agent workload is decided on the output column, not the input one everyone quotes.

Verifying the route

  1. Include /v1 in OPENAI_URL. The default already has it, unlike Spring AI's base-url.
  2. Clear the config cache after any .env change — php artisan config:clear locally, config:cache again in production.
  3. Address the provider by enum, Provider::OpenAI, not a string.
  4. Use usingProviderConfig() when switching endpoints per request; the inline argument does not re-resolve the provider.
  5. Raise request_timeout if you see client-side timeouts with nothing in the gateway's logs.

FAQ

FAQ

Should the URL include /v1?

Yes. The shipped default is https://api.openai.com/v1, and the provider appends the operation path. An unversioned URL 404s.

I changed .env and nothing happened

The config is cached. Run php artisan config:clear in development, or php artisan config:cache again after editing in production.

Why do requests time out with no error from my gateway?

request_timeout defaults to 30 seconds. Raise it in config/prism.php or pass ->withClientOptions(['timeout' => 120]).

When does the third argument to using() not apply?

When the provider has already been resolved. Use usingProviderConfig() instead, which re-resolves it.

Do I need to publish the config file?

Only to change defaults. php artisan vendor:publish --tag=prism-config creates config/prism.php; without it you are relying on the package defaults and environment variables.

Related

Get API access