Claude API PHP Integration Example (cURL & Guzzle)
If you're building a PHP application and want to call Claude directly, there's no official PHP SDK from Anthropic — so you integrate it the same way you'd integrate any HTTP API: with cURL or Guzzle, a valid API key, and the correct JSON payload structure. This article walks through a working example of both approaches, plus the request/response shape you need to handle correctly.
The short answer: Claude's API is a standard HTTPS JSON API. In PHP, you send a POST request to the messages endpoint with your API key in a header, a model field, a max_tokens field, and a messages array. You parse the JSON response to get the assistant's reply. No special framework is required — Laravel, Symfony, or plain PHP all work the same way since it's just HTTP.
Setting Up Authentication
Whether you're calling Anthropic's API directly or routing through a proxy layer, you need an API key stored as an environment variable — never hardcoded in your source files.
<?php
$apiKey = getenv('CLAUDE_API_KEY');
If you're managing multiple client applications or team members, it's worth considering a layer like SubToAPI that issues per-application keys (sub_live_...) from your existing Claude access, so you're not sharing one raw key across every PHP service you deploy. Key management becomes a real problem once you have more than one app calling Claude — SubToAPI handles that with a dashboard and scoped keys instead of manually rotating secrets.
Basic Integration With cURL
cURL ships with most PHP installations, so this is the lowest-friction way to get a working integration without adding dependencies.
<?php
$apiKey = getenv('CLAUDE_API_KEY');
$payload = [
'model' => 'claude-sonnet-4-5',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'Explain PHP traits in two sentences.']
],
];
$ch = curl_init('https://api.anthropic.com/v1/messages');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
'content-type: application/json',
'x-api-key: ' . $apiKey,
'anthropic-version: 2023-06-01',
],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new RuntimeException("API error: HTTP $httpCode - $response");
}
$data = json_decode($response, true);
echo $data['content'][0]['text'];
This is the complete loop: build the payload, send it, decode the JSON, pull the text out of content[0]['text']. The response also includes usage with input_tokens and output_tokens, which is worth logging if you're tracking cost per request.
Cleaner Integration With Guzzle
If your project already uses Composer, Guzzle gives you timeouts, retries, and exception handling without manually wrangling cURL options.
composer require guzzlehttp/guzzle
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'https://api.anthropic.com',
'timeout' => 30,
]);
try {
$response = $client->post('/v1/messages', [
'headers' => [
'content-type' => 'application/json',
'x-api-key' => getenv('CLAUDE_API_KEY'),
'anthropic-version' => '2023-06-01',
],
'json' => [
'model' => 'claude-sonnet-4-5',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'Write a PHP function that validates an email address.']
],
],
]);
$data = json_decode($response->getBody(), true);
echo $data['content'][0]['text'];
} catch (RequestException $e) {
error_log('Claude API request failed: ' . $e->getMessage());
}
Guzzle's built-in exception handling means you don't need to manually check HTTP status codes — a non-2xx response throws a RequestException you can catch and log.
Handling Multi-Turn Conversations
Claude doesn't maintain conversation state server-side — every request is stateless, so your PHP code needs to send the full message history each time.
$messages = [
['role' => 'user', 'content' => 'What is dependency injection?'],
['role' => 'assistant', 'content' => 'Dependency injection is a design pattern...'],
['role' => 'user', 'content' => 'Show me a PHP example.'],
];
Store this array in a session, database row, or Redis key depending on your app's architecture, and append new turns before each request.
Routing Through SubToAPI Instead
If you'd rather not manage raw Anthropic keys, rate limits, or usage tracking yourself, you can point the same PHP code at SubToAPI with almost no changes — just swap the base URL and header:
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.subtoapi.app/v1/messages',
CURLOPT_HTTPHEADER => [
'content-type: application/json',
'Authorization: Bearer ' . getenv('SUBTOAPI_KEY'),
],
]);
This is useful once you have multiple PHP services or a team sharing one Claude plan — SubToAPI gives each app its own sub_live_ key, tracks usage per key in a dashboard, and supports streaming and tool use through the same endpoint shape. Setup details are in the quickstart and full messages reference.
Error Handling You Shouldn't Skip
Three failure modes show up often enough in production PHP integrations that you should handle them explicitly:
- Rate limits (429) — back off and retry with exponential delay rather than failing the request immediately.
- Timeouts — Claude responses can take several seconds for longer outputs; set a generous
CURLOPT_TIMEOUTor Guzzletimeout. - Malformed JSON in responses — always check
json_decode()didn't returnnullbefore accessing array keys, especially under network instability.
if ($data === null) {
throw new RuntimeException('Failed to decode Claude API response');
}
Questions
Does Anthropic offer an official PHP SDK? No. Anthropic maintains official SDKs for Python and TypeScript/JavaScript. For PHP, you call the REST API directly with cURL, Guzzle, or any HTTP client — the integration pattern shown above is the standard approach.
What's the minimum payload needed to call Claude from PHP? You need model, max_tokens, and a messages array with at least one message containing a role and content. Headers require your API key and the anthropic-version header.
Can I stream Claude responses in a PHP app? Yes, using Server-Sent Events with cURL's streaming write function or Guzzle's stream option, parsing each data: chunk as it arrives. See streaming docs for the chunk format if you're integrating through SubToAPI.