Messages
The Messages API is the primary way to interact with Claude through this SDK. You send a list of messages (a conversation), and Claude responds with a new message.
Creating a message
Pass an array of parameters to create(). At minimum, you need a model, max_tokens limit, and at least one message:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'Hello, world'],
],
]);
The messages array represents a conversation. Each message has a role (user or assistant) and content (a string or an array of content blocks).
Response structure
The create() method returns a CreateResponse object. Here's what you get back:
$response->id; // 'msg_01BSy0WCV7QR2adFBauynAX7'
$response->type; // 'message'
$response->role; // 'assistant'
$response->model; // 'claude-sonnet-4-6'
$response->stop_reason; // 'end_turn'
$response->stop_sequence; // null
Content blocks
The response content is an array of typed blocks. For a simple text response, there's one block:
foreach ($response->content as $block) {
$block->type; // 'text'
$block->text; // 'Hello! It's nice to meet you. How can I assist you today?'
}
Different features produce different content block types. Tool Use adds tool_use blocks. Thinking adds thinking blocks. Server Tools add server_tool_use and result blocks.
Token usage
Every response includes token counts:
$response->usage->inputTokens; // 10
$response->usage->outputTokens; // 19
$response->usage->cacheCreationInputTokens; // 0
$response->usage->cacheReadInputTokens; // 0
If you're using prompt caching, the cache fields tell you how many tokens were written to or read from cache.
For cache breakdowns by window type:
$response->usage->cacheCreation; // null or CreateResponseUsageCacheCreation
$response->usage->cacheCreation?->ephemeral5mInputTokens; // 456
$response->usage->cacheCreation?->ephemeral1hInputTokens; // 100
The serviceTier field shows which processing tier handled the request:
$response->usage->serviceTier; // 'standard', 'priority', 'batch', or null
When using server tools, you can check tool usage counts:
$response->usage->serverToolUse; // null or CreateResponseUsageServerToolUse
$response->usage->serverToolUse?->webSearchRequests; // 3
Converting to an array
Every response object has a toArray() method that returns the raw data as a PHP array:
$response->toArray();
// ['id' => 'msg_01BSy0WCV7QR2adFBauynAX7', 'type' => 'message', ...]
This is useful for logging, serialization, or when you need to pass the response to code that expects plain arrays.
Multi-turn conversations
To have a back-and-forth conversation, include the full message history in each request. The API is stateless, so you need to send the entire conversation every time:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => 'What is PHP?'],
['role' => 'assistant', 'content' => 'PHP is a server-side scripting language...'],
['role' => 'user', 'content' => 'What version should I use?'],
],
]);
Messages must alternate between user and assistant roles. The conversation always starts with a user message.
System messages
Use the system parameter to give Claude instructions, persona, or context that applies to the whole conversation:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'system' => 'You are a helpful PHP expert. Answer concisely.',
'messages' => [
['role' => 'user', 'content' => 'What is the null coalescing operator?'],
],
]);
The system message isn't part of the messages array. It's a separate top-level parameter.
Vision
Claude can read images in the same request as text. Instead of a string, pass an array of content blocks in the message's content:
$imagePath = '/path/to/photo.jpg';
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'messages' => [
[
'role' => 'user',
'content' => [
[
'type' => 'text',
'text' => 'What is in this image?',
],
[
'type' => 'image',
'source' => [
'type' => 'base64',
'media_type' => 'image/jpeg',
'data' => base64_encode(file_get_contents($imagePath)),
],
],
],
],
],
]);
Supported media types are image/jpeg, image/png, image/gif, and image/webp. You can detect the MIME type from the file itself with PHP's finfo:
$mimeType = (new finfo(FILEINFO_MIME_TYPE))->file($imagePath);
You can also pass images by URL instead of embedding them:
[
'type' => 'image',
'source' => [
'type' => 'url',
'url' => 'https://example.com/photo.jpg',
],
]
Multiple images in a single message work too. Add as many image blocks as you need to the content array alongside the text block.
Tracking users
Pass a metadata object with a user_id to associate each request with a user in your system. These IDs appear in the Anthropic Console for analytics and abuse detection:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'metadata' => [
'user_id' => $user->uuid,
],
'messages' => [
['role' => 'user', 'content' => 'Hello!'],
],
]);
Use an opaque identifier like a UUID or hash. Don't send personal information like names or email addresses.
Passing parameters
This client doesn't validate or transform request parameters. Whatever you pass in the array goes directly to the Anthropic API as JSON. This means:
- New API parameters work immediately, even before the client adds explicit support
- You can pass any parameter documented in the Anthropic API reference
- If you pass an invalid parameter, the API returns an error (see Error Handling)
For example, setting temperature and top_p:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'temperature' => 0.7,
'top_p' => 0.9,
'messages' => [
['role' => 'user', 'content' => 'Write a haiku about PHP.'],
],
]);
Stop sequences
You can tell Claude to stop generating when it produces a specific string:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'stop_sequences' => ['```'],
'messages' => [
['role' => 'user', 'content' => 'Write a PHP function, then explain it.'],
],
]);
$response->stop_reason; // 'stop_sequence'
$response->stop_sequence; // '```'
When Claude hits a stop sequence, stop_reason is 'stop_sequence' and stop_sequence tells you which one matched.
Handling refusals
When safety classifiers intervene mid-generation, the API returns stop_reason: 'refusal' along with a stop_details object that explains why. The response is otherwise well-formed, so you can treat it like any other completion and just branch on stop_reason:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 1024,
'messages' => [
['role' => 'user', 'content' => $userPrompt],
],
]);
if ($response->stop_reason === 'refusal') {
$response->stop_details->type; // 'refusal'
$response->stop_details->category; // 'cyber', 'bio', or null
$response->stop_details->explanation; // human-readable text, or null
}
stop_details is only populated on refusal responses; on a normal completion it's null. The explanation text isn't guaranteed to be stable between requests, so don't parse it. Treat category as the machine-readable signal and show explanation to the user if you need to display something.
The pause_turn stop reason
Long-running turns that hit internal limits come back with stop_reason: 'pause_turn' instead of 'end_turn'. The response is valid and complete for the work done so far. To let Claude continue, send the same response back as the next assistant message:
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 8192,
'messages' => $messages,
]);
while ($response->stop_reason === 'pause_turn') {
$messages[] = [
'role' => 'assistant',
'content' => array_map(fn ($block) => $block->toArray(), $response->content),
];
$response = $client->messages()->create([
'model' => 'claude-sonnet-4-6',
'max_tokens' => 8192,
'messages' => $messages,
]);
}
You don't need any special parameter to resume, just echo the paused content back in the next turn. If you don't want to resume, leave it as is and treat it like a normal reply.
Inference region
Responses include the geographic region that processed the request:
$response->usage->inferenceGeo; // 'us', 'eu', or null
Useful for data-residency logging or routing decisions. The field is null when the API doesn't report a region.
For the full list of parameters, content block types, and the latest API changes, see the Messages API reference on the Anthropic docs.