Claude API PHP & Laravel Tutorial: Complete Integration Guide
The Claude API has no official PHP SDK, but integration is straightforward using Guzzle or Laravel's HTTP client — both support streaming responses, and the REST API is identical to what Python and Node.js clients use. A basic PHP call to https://api.anthropic.com/v1/messages with an x-api-key header and a JSON body is all you need to start. This guide walks you through everything from a plain PHP cURL call to a full Laravel service layer with queued jobs and streaming.
Quick Start: Plain PHP with cURL
No dependencies required for a basic call:
<?php
$apiKey = getenv('ANTHROPIC_API_KEY');
$data = [
'model' => 'claude-sonnet-4-5',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'Explain what a PHP service container does.']
]
];
$ch = curl_init('https://api.anthropic.com/v1/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'x-api-key: ' . $apiKey,
'anthropic-version: 2023-06-01',
'content-type: application/json',
],
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
echo $result['content'][0]['text'];
Key headers required on every request:
x-api-key— your Anthropic API keyanthropic-version: 2023-06-01— stable API versioncontent-type: application/json
Using Guzzle (Recommended for PHP Projects)
composer require guzzlehttp/guzzle
<?php
use GuzzleHttp\Client;
class ClaudeClient
{
private Client $http;
private string $apiKey;
private string $baseUrl = 'https://api.anthropic.com/v1';
public function __construct(string $apiKey)
{
$this->apiKey = $apiKey;
$this->http = new Client([
'base_uri' => $this->baseUrl,
'headers' => [
'x-api-key' => $this->apiKey,
'anthropic-version' => '2023-06-01',
'content-type' => 'application/json',
],
'timeout' => 60,
]);
}
public function message(string $userMessage, string $model = 'claude-sonnet-4-5'): string
{
$response = $this->http->post('/messages', [
'json' => [
'model' => $model,
'max_tokens' => 2048,
'messages' => [
['role' => 'user', 'content' => $userMessage]
],
],
]);
$body = json_decode($response->getBody()->getContents(), true);
return $body['content'][0]['text'];
}
}
// Usage
$client = new ClaudeClient(getenv('ANTHROPIC_API_KEY'));
echo $client->message('What are the SOLID principles?');
Laravel Integration
Service Provider Setup
// app/Providers/AppServiceProvider.php
use App\Services\ClaudeService;
public function register(): void
{
$this->app->singleton(ClaudeService::class, function () {
return new ClaudeService(config('services.claude.api_key'));
});
}
// config/services.php
'claude' => [
'api_key' => env('ANTHROPIC_API_KEY'),
'model' => env('CLAUDE_MODEL', 'claude-sonnet-4-5'),
],
Laravel HTTP Client (No Guzzle Needed)
Laravel's Http facade wraps Guzzle and is already installed:
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class ClaudeService
{
private string $apiKey;
private string $model;
public function __construct(string $apiKey, string $model = 'claude-sonnet-4-5')
{
$this->apiKey = $apiKey;
$this->model = $model;
}
public function chat(string $userMessage, ?string $systemPrompt = null): string
{
$payload = [
'model' => $this->model,
'max_tokens' => 2048,
'messages' => [
['role' => 'user', 'content' => $userMessage]
],
];
if ($systemPrompt) {
$payload['system'] = $systemPrompt;
}
$response = Http::withHeaders([
'x-api-key' => $this->apiKey,
'anthropic-version' => '2023-06-01',
])->timeout(60)->post('https://api.anthropic.com/v1/messages', $payload);
$response->throw(); // Throw on 4xx/5xx
return $response->json('content.0.text');
}
}
Controller Example
<?php
namespace App\Http\Controllers;
use App\Services\ClaudeService;
use Illuminate\Http\Request;
class ContentController extends Controller
{
public function __construct(private ClaudeService $claude) {}
public function generate(Request $request): \Illuminate\Http\JsonResponse
{
$validated = $request->validate([
'prompt' => 'required|string|max:2000',
'context' => 'nullable|string|max:500',
]);
$response = $this->claude->chat(
userMessage: $validated['prompt'],
systemPrompt: $validated['context'] ?? null
);
return response()->json(['result' => $response]);
}
}
Streaming Responses in PHP
Streaming lets you display tokens as they arrive — critical for chat-like UX:
public function stream(string $userMessage): void
{
$ch = curl_init('https://api.anthropic.com/v1/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'model' => 'claude-sonnet-4-5',
'max_tokens' => 2048,
'stream' => true,
'messages' => [['role' => 'user', 'content' => $userMessage]],
]),
CURLOPT_HTTPHEADER => [
'x-api-key: ' . $this->apiKey,
'anthropic-version: 2023-06-01',
'content-type: application/json',
],
CURLOPT_WRITEFUNCTION => function ($ch, $data) {
// Each chunk is a Server-Sent Event line
foreach (explode("\n", $data) as $line) {
if (str_starts_with($line, 'data: ')) {
$json = json_decode(substr($line, 6), true);
if (isset($json['delta']['text'])) {
echo $json['delta']['text'];
flush();
}
}
}
return strlen($data);
},
]);
curl_exec($ch);
curl_close($ch);
}
For Laravel + Server-Sent Events, return a StreamedResponse:
public function streamResponse(Request $request): \Symfony\Component\HttpFoundation\StreamedResponse
{
return response()->stream(function () use ($request) {
$this->claude->stream($request->input('prompt'));
}, 200, [
'Content-Type' => 'text/event-stream',
'Cache-Control' => 'no-cache',
'X-Accel-Buffering' => 'no', // Nginx: disable buffering
]);
}
Queued Laravel Jobs for Long Requests
For tasks that take more than a few seconds, use a queued job:
<?php
namespace App\Jobs;
use App\Services\ClaudeService;
use App\Models\ContentRequest;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class GenerateContentJob implements ShouldQueue
{
use Dispatchable, Queueable;
public int $timeout = 120;
public int $tries = 3;
public function __construct(
private int $requestId,
private string $prompt
) {}
public function handle(ClaudeService $claude): void
{
$result = $claude->chat($this->prompt);
ContentRequest::find($this->requestId)?->update([
'result' => $result,
'status' => 'completed',
'completed_at' => now(),
]);
}
}
Benchmark: In testing against the Claude Sonnet 4.5 model, a typical 500-token response takes 3–8 seconds. Queuing tasks over 5 seconds prevents PHP-FPM timeouts and allows retries on transient API errors.
Mid-Article Offer
Building agents, content pipelines, or API integrations on top of Claude? The Agent SDK Cookbook ($49) includes 15 production-ready agent implementations with error handling, retry logic, and cost tracking — patterns you can adapt to any language including PHP.
→ Get the Agent SDK Cookbook — $49
Error Handling and Rate Limits
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
public function safeChat(string $message): string
{
$maxRetries = 3;
for ($attempt = 0; $attempt < $maxRetries; $attempt++) {
try {
return $this->chat($message);
} catch (ClientException $e) {
$status = $e->getResponse()->getStatusCode();
$body = json_decode($e->getResponse()->getBody()->getContents(), true);
if ($status === 429) {
sleep((int) ($e->getResponse()->getHeader('retry-after')[0] ?? 5));
} elseif ($status === 401) {
throw new \RuntimeException('Invalid API key: ' . ($body['error']['message'] ?? ''));
} elseif ($status === 400) {
throw new \InvalidArgumentException('Bad request: ' . ($body['error']['message'] ?? ''));
} else {
throw $e;
}
} catch (ServerException $e) {
if ($attempt === $maxRetries - 1) throw $e;
sleep(2 ** $attempt); // Exponential backoff
}
}
throw new \RuntimeException('Max retries exceeded');
}
See the Claude API error codes reference for a full list of status codes and their meanings.
Related Guides
- Claude Agent SDK Guide — Agentic loop patterns, tool use, and production deployment in Python and TypeScript
- Claude Code Complete Guide — CLI-driven development workflows with Claude
- Claude API Cost Monitoring Guide — Track and optimize API spend
- Structured Outputs and JSON with Claude — Guarantee JSON responses
Frequently Asked Questions
Is there an official Claude PHP SDK?
No — as of 2026, Anthropic does not publish an official PHP SDK. PHP integrations use the REST API directly via cURL, Guzzle, or Laravel's HTTP client. The REST API is identical across all languages, so all features (streaming, tool use, vision) are available.
What PHP version is required?
PHP 8.1+ is recommended (for named arguments, enums, and fibers). PHP 7.4 works for basic calls, but streaming with fibers requires PHP 8.1+. Laravel 11 requires PHP 8.2+.
How do I handle streaming in a Laravel controller?
Return a StreamedResponse with Content-Type: text/event-stream and set X-Accel-Buffering: no if behind Nginx. Use ob_flush() and flush() inside the write callback to push data to the browser incrementally.
Can I use Claude API with Livewire or Inertia.js?
Yes. For Livewire, trigger the API call from a component action and update a public property. For Inertia.js, create a dedicated API endpoint and call it via axios from your Vue/React component. Streaming works best with a dedicated SSE endpoint separate from your Inertia routes.
How do I keep API keys out of the codebase?
Store ANTHROPIC_API_KEY in your .env file (never commit it). Use config('services.claude.api_key') in Laravel rather than env() directly in code. On production, set the key as an environment variable in your hosting platform (Forge, Vapor, Railway, etc.).
What is the cost of calling the Claude API from PHP?
Claude Sonnet 4.5 costs $3/M input tokens and $15/M output tokens. A typical 500-word response is roughly 700 output tokens, costing about $0.0105. For high-volume use, see the model comparison guide — Haiku is 25x cheaper and sufficient for many tasks.
Go Deeper
Agent SDK Cookbook — $49 — 15 complete agent implementations ready for production: customer support, data pipeline, code review, DevOps monitoring, and sales outreach agents. Language-agnostic patterns you can port to PHP.
→ Get the Agent SDK Cookbook — $49
30-day money-back guarantee. Instant download.