PHP Integration
Also available in: Python · Node.js · Go
The "gateway base URL" is the address of your Observra Gateway (https://gateway.observra.in) - your app sends requests here instead of directly to OpenAI, Anthropic, or whichever provider you use. The "gateway key" is a per-Application credential you generate in the Observra Dashboard; it authenticates your app to the gateway and is separate from your provider's own API key, which you still keep and send yourself. See Quickstart and Gateway Authentication if you haven't set these up yet. Note: the tested PHP examples below don't include trace propagation - trace passing wasn't part of the source scripts for PHP.
Try it now
Paste in a gateway URL, your gateway key, and a provider key to send a real request straight from this page - no code required yet.
Setup
Assume these values already exist in your app (load them from environment variables or your framework's config, don't hardcode them):
$gatewayBase = 'https://gateway.observra.in';
$gatewayKey = 'observra_gateway_key';
$providerKey = 'provider_api_key';
Raw HTTP requests (cURL)
Every raw cURL call adds X-Gateway-Key for the gateway and X-Provider-Key for the upstream provider, then POSTs to the gateway route.
- OpenAI
- Anthropic
- Gemini
- Groq
- Ollama
- OpenRouter
- Azure OpenAI
$payload = [
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
];
$ch = curl_init($gatewayBase.'/openai/v1/chat/completions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Gateway-Key: '.$gatewayKey,
'X-Provider-Key: '.$providerKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 60,
]);
$data = json_decode(curl_exec($ch), true);
$text = $data['choices'][0]['message']['content'] ?? '';
$payload = [
'model' => 'claude-sonnet-4-6',
'max_tokens' => 128,
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
];
$ch = curl_init($gatewayBase.'/anthropic/v1/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Gateway-Key: '.$gatewayKey,
'X-Provider-Key: '.$providerKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 60,
]);
$data = json_decode(curl_exec($ch), true);
$text = $data['content'][0]['text'] ?? '';
$payload = [
'contents' => [[
'role' => 'user',
'parts' => [['text' => 'Say hello in one line.']],
]],
];
$ch = curl_init($gatewayBase.'/gemini/v1beta/models/gemini-3.1-flash-lite:generateContent');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Gateway-Key: '.$gatewayKey,
'X-Provider-Key: '.$providerKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 60,
]);
$data = json_decode(curl_exec($ch), true);
$text = $data['candidates'][0]['content']['parts'][0]['text'] ?? '';
$payload = [
'model' => 'llama-3.3-70b-versatile',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
];
$ch = curl_init($gatewayBase.'/groq/v1/chat/completions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Gateway-Key: '.$gatewayKey,
'X-Provider-Key: '.$providerKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 60,
]);
$data = json_decode(curl_exec($ch), true);
$text = $data['choices'][0]['message']['content'] ?? '';
$payload = [
'model' => 'gpt-oss:120b-cloud',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
];
$ch = curl_init($gatewayBase.'/ollama/v1/chat/completions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Gateway-Key: '.$gatewayKey,
'X-Provider-Key: '.$providerKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 60,
]);
$data = json_decode(curl_exec($ch), true);
$text = $data['choices'][0]['message']['content'] ?? '';
$payload = [
'model' => 'cohere/north-mini-code:free',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
];
$ch = curl_init($gatewayBase.'/openrouter/v1/chat/completions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Gateway-Key: '.$gatewayKey,
'X-Provider-Key: '.$providerKey,
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 60,
]);
$data = json_decode(curl_exec($ch), true);
$text = $data['choices'][0]['message']['content'] ?? '';
$payload = [
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
];
$ch = curl_init($gatewayBase.'/azure/v1/chat/completions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Gateway-Key: '.$gatewayKey,
'X-Provider-Key: '.$providerKey,
'X-Azure-Endpoint: https://your-resource.openai.azure.com',
'X-Azure-Api-Version: 2024-02-15-preview',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
CURLOPT_TIMEOUT => 60,
]);
$data = json_decode(curl_exec($ch), true);
$text = $data['choices'][0]['message']['content'] ?? '';
Native SDKs
Install:
composer require openai-php/client anthropic-ai/sdk google-gemini-php/client guzzlehttp/guzzle
- OpenAI SDK
- Anthropic SDK
- Gemini SDK
- Groq SDK
- Ollama SDK
- OpenRouter SDK
- Azure OpenAI SDK
$client = OpenAI::factory()
->withApiKey($providerKey)
->withBaseUri($gatewayBase.'/openai/v1')
->withHttpHeader('X-Gateway-Key', $gatewayKey)
->withHttpHeader('X-Provider-Key', $providerKey)
->make();
$response = $client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
]);
$text = $response->choices[0]->message->content ?? '';
putenv('ANTHROPIC_API_KEY='.$providerKey);
putenv('ANTHROPIC_BASE_URL='.$gatewayBase.'/anthropic');
$client = new Anthropic\Client(
requestOptions: Anthropic\RequestOptions::with(
extraHeaders: [
'X-Gateway-Key' => $gatewayKey,
'X-Provider-Key' => $providerKey,
],
),
);
$response = $client->messages->create(
maxTokens: 256,
model: 'claude-sonnet-4-6',
messages: [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
);
$text = '';
foreach ($response->content as $block) {
$text .= $block->text ?? '';
}
$client = Gemini::factory()
->withApiKey($providerKey)
->withBaseUrl($gatewayBase.'/gemini/v1beta/')
->withHttpClient(new GuzzleHttp\Client())
->withHttpHeader('X-Gateway-Key', $gatewayKey)
->withHttpHeader('X-Provider-Key', $providerKey)
->make();
$response = $client
->generativeModel(model: 'gemini-3.1-flash-lite')
->generateContent('Say hello in one line.');
$text = $response->text();
$client = OpenAI::factory()
->withApiKey($providerKey)
->withBaseUri($gatewayBase.'/groq/v1')
->withHttpHeader('X-Gateway-Key', $gatewayKey)
->withHttpHeader('X-Provider-Key', $providerKey)
->make();
$response = $client->chat()->create([
'model' => 'llama-3.3-70b-versatile',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
]);
$text = $response->choices[0]->message->content ?? '';
$client = OpenAI::factory()
->withApiKey($providerKey)
->withBaseUri($gatewayBase.'/ollama/v1')
->withHttpHeader('X-Gateway-Key', $gatewayKey)
->withHttpHeader('X-Provider-Key', $providerKey)
->make();
$response = $client->chat()->create([
'model' => 'gpt-oss:120b-cloud',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
]);
$text = $response->choices[0]->message->content ?? '';
$client = OpenAI::factory()
->withApiKey($providerKey)
->withBaseUri($gatewayBase.'/openrouter/v1')
->withHttpHeader('X-Gateway-Key', $gatewayKey)
->withHttpHeader('X-Provider-Key', $providerKey)
->make();
$response = $client->chat()->create([
'model' => 'cohere/north-mini-code:free',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
]);
$text = $response->choices[0]->message->content ?? '';
$client = OpenAI::factory()
->withApiKey($providerKey)
->withBaseUri($gatewayBase.'/azure/v1')
->withHttpHeader('X-Gateway-Key', $gatewayKey)
->withHttpHeader('X-Provider-Key', $providerKey)
->withHttpHeader('X-Azure-Endpoint', 'https://your-resource.openai.azure.com')
->withHttpHeader('X-Azure-Api-Version', '2024-02-15-preview')
->make();
$response = $client->chat()->create([
'model' => 'gpt-4o-mini',
'messages' => [
['role' => 'user', 'content' => 'Say hello in one line.'],
],
]);
$text = $response->choices[0]->message->content ?? '';
Agent frameworks & SDKs
More advanced: full PHP frameworks that wrap one or more providers behind their own client abstraction. Each still needs X-Gateway-Key and X-Provider-Key set on the underlying HTTP client, plus X-Azure-Endpoint/X-Azure-Api-Version for Azure.
- Laravel AI
- LLPhant
- Neuron AI
- Prism
- Symfony AI Platform
$httpFactory->globalOptions([
'headers' => [
'X-Gateway-Key' => $gatewayKey,
'X-Provider-Key' => $providerKey,
],
]);
$agent = new Laravel\Ai\AnonymousAgent('Reply with a short greeting only.', [], []);
$response = $provider->prompt(new Laravel\Ai\Prompts\AgentPrompt(
$agent,
'Say hello in one line.',
[],
$provider,
$model,
60,
));
$text = $response->text;
OpenAI provider setup:
$config = [
'driver' => 'openai',
'key' => $providerKey,
'url' => $gatewayBase.'/openai/v1',
'models' => ['text' => ['default' => 'gpt-4o-mini']],
];
$provider = new Laravel\Ai\Providers\OpenAiProvider(
new Laravel\Ai\Gateway\OpenAi\OpenAiGateway($events),
$config,
$events,
);
$model = 'gpt-4o-mini';
Anthropic, Gemini, Groq, Ollama, and OpenRouter follow the same pattern with their matching Laravel\Ai\Providers\*Provider class and url set to $gatewayBase.'/<provider>/v1' (or /v1beta for Gemini, no suffix for Ollama). Azure needs the extra headers on $httpFactory plus 'driver' => 'azure', 'url' => $gatewayBase.'/azure', and 'api_version'/'deployment' keys.
OpenAI-compatible providers:
use GuzzleHttp\Client as GuzzleClient;
use LLPhant\Chat\OpenAIChat;
use LLPhant\OpenAIConfig;
$openAiClient = OpenAI::factory()
->withHttpClient(new GuzzleClient(['verify' => false]))
->withApiKey($providerKey)
->withBaseUri($baseUri)
->withHttpHeader('X-Gateway-Key', $gatewayKey)
->withHttpHeader('X-Provider-Key', $providerKey)
->make();
$config = new OpenAIConfig($providerKey, $baseUri, $model, $openAiClient);
$chat = new OpenAIChat($config);
$text = $chat->generateText('Say hello in one line.');
$baseUri/$model per provider:
// OpenAI
$baseUri = $gatewayBase.'/openai/v1'; $model = 'gpt-4o-mini';
// Gemini
$baseUri = $gatewayBase.'/gemini/v1beta/openai'; $model = 'gemini-3.1-flash-lite';
// Groq
$baseUri = $gatewayBase.'/groq/v1'; $model = 'llama-3.3-70b-versatile';
// Ollama
$baseUri = $gatewayBase.'/ollama/v1'; $model = 'gpt-oss:120b-cloud';
// OpenRouter
$baseUri = $gatewayBase.'/openrouter/v1'; $model = 'cohere/north-mini-code:free';
// Azure OpenAI
$baseUri = $gatewayBase.'/azure/v1'; $model = 'gpt-4o-mini';
Anthropic needs a custom PSR-18 client since LLPhant's Anthropic support expects one:
use GuzzleHttp\Client as GuzzleClient;
use LLPhant\AnthropicConfig;
use LLPhant\Chat\AnthropicChat;
use Psr\Http\Client\ClientInterface;
$httpClient = new class($gatewayBase, $gatewayKey, $providerKey) implements ClientInterface {
public function __construct(
private string $gatewayBase,
private string $gatewayKey,
private string $providerKey,
) {}
public function sendRequest(Psr\Http\Message\RequestInterface $request): Psr\Http\Message\ResponseInterface
{
$client = new GuzzleClient([
'base_uri' => $this->gatewayBase.'/anthropic/',
'headers' => [
'X-Gateway-Key' => $this->gatewayKey,
'X-Provider-Key' => $this->providerKey,
],
'http_errors' => false,
'timeout' => 60,
]);
$uri = $request->getUri()->withScheme('')->withHost('')->withPort(null)->withPath('v1/messages');
return $client->send($request->withUri($uri));
}
};
$config = new AnthropicConfig(
model: 'claude-sonnet-4-6',
maxTokens: 128,
apiKey: $providerKey,
client: $httpClient,
);
$chat = new AnthropicChat($config);
$text = $chat->generateText('Say hello in one line.');
OpenAI-compatible providers:
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\HttpClient\GuzzleHttpClient;
use NeuronAI\Providers\OpenAILike;
$httpClient = (new GuzzleHttpClient(options: ['verify' => false]))
->withHeaders([
'X-Gateway-Key' => $gatewayKey,
'X-Provider-Key' => $providerKey,
]);
$llm = (new OpenAILike($baseUri, $providerKey, $model, [], false, $httpClient))
->systemPrompt('Reply with a short greeting only.');
$reply = $llm->chat(new UserMessage('Say hello in one line.'));
$text = $reply->getContent();
$baseUri/$model per provider:
// OpenAI
$baseUri = $gatewayBase.'/openai/v1'; $model = 'gpt-4o-mini';
// Groq
$baseUri = $gatewayBase.'/groq/v1'; $model = 'llama-3.3-70b-versatile';
// OpenRouter
$baseUri = $gatewayBase.'/openrouter/v1'; $model = 'cohere/north-mini-code:free';
// Azure OpenAI (add X-Azure-Endpoint / X-Azure-Api-Version to $httpClient headers)
$baseUri = $gatewayBase.'/azure/v1'; $model = 'gpt-4o-mini';
Anthropic and Gemini/Ollama use dedicated provider classes instead of OpenAILike:
// Anthropic
$httpClient = (new NeuronAI\HttpClient\GuzzleHttpClient(options: ['verify' => false]))
->withHeaders(['X-Gateway-Key' => $gatewayKey, 'X-Provider-Key' => $providerKey]);
$llm = (new NeuronAI\Providers\Anthropic\Anthropic(
$providerKey, 'claude-sonnet-4-6', '2023-06-01', 8192, [], $httpClient,
))->systemPrompt('Reply with a short greeting only.');
$reply = $llm->chat(new NeuronAI\Chat\Messages\UserMessage('Say hello in one line.'));
// Gemini
$httpClient = (new NeuronAI\HttpClient\GuzzleHttpClient(options: ['verify' => false]))
->withBaseUri($gatewayBase.'/gemini/v1beta/models')
->withHeaders(['X-Gateway-Key' => $gatewayKey, 'X-Provider-Key' => $providerKey]);
$llm = (new NeuronAI\Providers\Gemini\Gemini(
$providerKey, 'gemini-3.1-flash-lite', [], $httpClient, $gatewayBase.'/gemini/v1beta/models',
))->systemPrompt('Reply with a short greeting only.');
// Ollama
$httpClient = (new NeuronAI\HttpClient\GuzzleHttpClient(options: ['verify' => false]))
->withBaseUri($gatewayBase.'/ollama/api')
->withHeaders(['X-Gateway-Key' => $gatewayKey, 'X-Provider-Key' => $providerKey]);
$llm = (new NeuronAI\Providers\Ollama\Ollama(
$gatewayBase.'/ollama/api', 'gpt-oss:120b-cloud', ['stream' => false], $httpClient,
))->systemPrompt('Reply with a short greeting only.');
Common request shape:
$request = new Prism\Prism\Text\Request(
model: $model,
providerKey: $providerKeyName,
systemPrompts: [new Prism\Prism\ValueObjects\Messages\SystemMessage('Reply with a short greeting only.')],
prompt: null,
messages: [new Prism\Prism\ValueObjects\Messages\UserMessage('Say hello in one line.')],
maxSteps: 1,
maxTokens: 128,
temperature: null,
topP: null,
tools: [],
clientOptions: [
'headers' => [
'X-Gateway-Key' => $gatewayKey,
'X-Provider-Key' => $providerKey,
],
'verify' => false,
],
clientRetry: [0],
toolChoice: null,
providerOptions: [],
providerTools: [],
);
$response = $provider->text($request);
$text = $response->text;
Provider setup:
// OpenAI
$providerKeyName = 'openai'; $model = 'gpt-4o-mini';
$provider = new Prism\Prism\Providers\OpenAI\OpenAI($providerKey, $gatewayBase.'/openai/v1', null, null);
// Anthropic
$providerKeyName = 'anthropic'; $model = 'claude-sonnet-4-6';
$provider = new Prism\Prism\Providers\Anthropic\Anthropic($providerKey, '2023-06-01', $gatewayBase.'/anthropic/v1', null);
// Gemini
$providerKeyName = 'gemini'; $model = 'gemini-3.1-flash-lite';
$provider = new Prism\Prism\Providers\Gemini\Gemini($providerKey, $gatewayBase.'/gemini/v1beta/models');
// Groq
$providerKeyName = 'groq'; $model = 'llama-3.3-70b-versatile';
$provider = new Prism\Prism\Providers\Groq\Groq($providerKey, $gatewayBase.'/groq/v1');
// Ollama
$providerKeyName = 'ollama'; $model = 'gpt-oss:120b-cloud';
$provider = new Prism\Prism\Providers\Ollama\Ollama($providerKey, $gatewayBase.'/ollama');
// OpenRouter
$providerKeyName = 'openrouter'; $model = 'cohere/north-mini-code:free';
$provider = new Prism\Prism\Providers\OpenRouter\OpenRouter($providerKey, $gatewayBase.'/openrouter/v1', null, null);
// Azure OpenAI
$providerKeyName = 'azure'; $model = 'gpt-4o-mini';
$provider = new Prism\Prism\Providers\OpenAI\OpenAI($providerKey, $gatewayBase.'/azure/v1', null, null);
Common setup:
use Symfony\AI\Platform\Message\Content\Text;
use Symfony\AI\Platform\Message\MessageBag;
use Symfony\AI\Platform\Message\SystemMessage;
use Symfony\AI\Platform\Message\UserMessage;
use Symfony\Component\HttpClient\HttpClient;
$httpClient = HttpClient::create([
'headers' => [
'X-Gateway-Key' => $gatewayKey,
'X-Provider-Key' => $providerKey,
],
]);
$messages = new MessageBag(
new SystemMessage('Reply with a short greeting only.'),
new UserMessage(new Text('Say hello in one line.')),
);
$result = $platform->invoke($model, $messages);
$text = $result->asText();
Platform factory per provider:
// OpenAI
$platform = Symfony\AI\Platform\Bridge\Generic\Factory::createPlatform($gatewayBase.'/openai', $providerKey, $httpClient, name: 'openai');
$model = 'gpt-4o-mini';
// Anthropic
$platform = Symfony\AI\Platform\Bridge\Anthropic\Factory::createPlatform($providerKey, $httpClient, name: 'anthropic', baseUrl: $gatewayBase.'/anthropic');
$model = 'claude-sonnet-4-6';
// Gemini
$platform = Symfony\AI\Platform\Bridge\Generic\Factory::createPlatform($gatewayBase.'/gemini/v1beta/openai', $providerKey, $httpClient, name: 'gemini');
$model = 'gemini-3.1-flash-lite';
// Groq
$platform = Symfony\AI\Platform\Bridge\Generic\Factory::createPlatform($gatewayBase.'/groq', $providerKey, $httpClient, name: 'groq');
$model = 'llama-3.3-70b-versatile';
// Ollama
$platform = Symfony\AI\Platform\Bridge\Ollama\Factory::createPlatform($gatewayBase.'/ollama', $providerKey, $httpClient, name: 'ollama');
$model = new Symfony\AI\Platform\Bridge\Ollama\Ollama(
'gpt-oss:120b-cloud',
[Symfony\AI\Platform\Capability::INPUT_MESSAGES, Symfony\AI\Platform\Capability::OUTPUT_TEXT],
);
// OpenRouter
$platform = Symfony\AI\Platform\Bridge\OpenRouter\Factory::createPlatform($providerKey, $httpClient, name: 'openrouter', baseUrl: $gatewayBase.'/openrouter');
$model = 'cohere/north-mini-code:free';
// Azure OpenAI (needs X-Azure-Endpoint / X-Azure-Api-Version on $httpClient headers)
$platform = Symfony\AI\Platform\Bridge\Generic\Factory::createPlatform($gatewayBase.'/azure', $providerKey, $httpClient, name: 'azure');
$model = 'gpt-4o-mini';
Notes
- Use
X-Gateway-Keyfor Observra Gateway auth,X-Provider-Keyfor the upstream provider's own auth. - Azure also needs
X-Azure-EndpointandX-Azure-Api-Versionheaders. - SDK/framework base URLs usually stop at the provider's base path; the SDK appends the endpoint path (e.g.
/chat/completions) internally.
Next steps
- OpenAI SDK Integration - the reference JavaScript/TypeScript walkthrough.
- Other Providers - full provider/URL-segment reference.
- Gateway Authentication - key precedence and header details.