claude-code-system-prompts/system-prompts/data-claude-api-reference-php.md
2026-06-18 17:15:00 -06:00

5.7 KiB

Claude API — PHP

Note: The PHP SDK is the official Anthropic SDK for PHP. A beta tool runner is available via $client->beta->messages->toolRunner(). Structured output helpers are supported via StructuredOutputModel classes. Agent SDK is not available. Bedrock, Vertex AI, and Foundry clients are supported.

Installation

composer require "anthropic-ai/sdk"

Client Initialization

use Anthropic\Client;

// Using API key from environment variable
$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));

Amazon Bedrock

use Anthropic\Bedrock\MantleClient;

// Messages-API Bedrock endpoint. Reads AWS credentials from env.
$client = new MantleClient(awsRegion: 'us-east-1');

Model IDs on Bedrock take an anthropic. prefix — e.g. model: 'anthropic.{{OPUS_ID}}'.

Google Vertex AI

use Anthropic\Vertex;

// Constructor is private. Parameter is `location`, not `region`.
$client = Vertex\Client::fromEnvironment(
    location: 'us-east5',
    projectId: 'my-project-id',
);

Anthropic Foundry

use Anthropic\Foundry;

// Constructor is private. baseUrl or resource is required.
$client = Foundry\Client::withCredentials(
    apiKey: getenv('ANTHROPIC_FOUNDRY_API_KEY'),
    baseUrl: 'https://<resource>.services.ai.azure.com/anthropic/v1',
);

Basic Message Request

$message = $client->messages->create(
    model: '{{OPUS_ID}}',
    maxTokens: 16000,
    messages: [
        ['role' => 'user', 'content' => 'What is the capital of France?'],
    ],
);

// content is an array of polymorphic blocks (TextBlock, ToolUseBlock,
// ThinkingBlock). Accessing ->text on content[0] without checking the block
// type will throw if the first block is not a TextBlock (e.g., when extended
// thinking is enabled and a ThinkingBlock comes first). Always guard:
foreach ($message->content as $block) {
    if ($block->type === 'text') {
        echo $block->text;
    }
}

If you only want the first text block:

foreach ($message->content as $block) {
    if ($block->type === 'text') {
        echo $block->text;
        break;
    }
}

Extended Thinking

Adaptive thinking is the recommended mode for Claude 4.6+ models. Claude decides dynamically when and how much to think.

use Anthropic\Messages\ThinkingBlock;

$message = $client->messages->create(
    model: '{{OPUS_ID}}',
    maxTokens: 16000,
    thinking: ['type' => 'adaptive', 'display' => 'summarized'], // display opt-in: default is omitted (empty thinking text) on Fable 5 / Mythos 5 / Opus 4.8 / 4.7
    messages: [
        ['role' => 'user', 'content' => 'Solve: 27 * 453'],
    ],
);

// ThinkingBlock(s) precede TextBlock in content
foreach ($message->content as $block) {
    if ($block instanceof ThinkingBlock) {
        echo "Thinking:\n{$block->thinking}\n\n";
        // $block->signature is an opaque string — preserve verbatim if
        // passing thinking blocks back in multi-turn conversations
    } elseif ($block->type === 'text') {
        echo "Answer: {$block->text}\n";
    }
}

Fable 5, Opus 4.8, Opus 4.7, Opus 4.6, and Sonnet 4.6: Use adaptive thinking (above). ['type' => 'enabled', 'budgetTokens' => N] is removed on Fable 5, Opus 4.8, and 4.7 (400 if sent); deprecated on Opus 4.6 and Sonnet 4.6. Older models: Use thinking: ['type' => 'enabled', 'budgetTokens' => N] (budget must be < maxTokens, min 1024).

$block->type === 'thinking' also works for the check; instanceof narrows for PHPStan.


Prompt Caching

system: takes an array of text blocks; set cacheControl on the last block. Array-shape syntax (camelCase keys) is idiomatic. For placement patterns and the silent-invalidator audit checklist, see shared/prompt-caching.md.

$message = $client->messages->create(
    model: '{{OPUS_ID}}',
    maxTokens: 16000,
    system: [
        ['type' => 'text', 'text' => $longSystemPrompt, 'cacheControl' => ['type' => 'ephemeral']],
    ],
    messages: [['role' => 'user', 'content' => 'Summarize the key points']],
);

For 1-hour TTL: 'cacheControl' => ['type' => 'ephemeral', 'ttl' => '1h']. There's also a top-level cacheControl: on messages->create(...) that auto-places on the last cacheable block.

Verify hits via $message->usage->cacheCreationInputTokens / $message->usage->cacheReadInputTokens.


Stop Details

When stopReason is 'refusal', the response includes structured stopDetails:

if ($message->stopReason === 'refusal' && $message->stopDetails !== null) {
    echo "Category: " . $message->stopDetails->category . "\n";     // e.g. "cyber", "bio", "reasoning_extraction", "frontier_llm", or null — see docs for the full set
    echo "Explanation: " . $message->stopDetails->explanation . "\n";
}

Refusal fallbacks ({{FABLE_NAME}}) — opt in by default. Fallbacks are opt-in: without them a refused request simply stops. New {{FABLE_ID}} code should include the server-side fallbacks parameter (beta header server-side-fallback-2026-06-01, fallback model {{OPUS_ID}}, on the beta messages call) by default. The exact PHP binding (and the client-side middleware for providers without server-side support) is not documented here — WebFetch the PHP SDK repo's examples/ from shared/live-sources.md; full semantics in shared/model-migration.md → Migrating to {{FABLE_NAME}} → refusal stop reason.


Error Type

APIStatusException exposes a ->type property for programmatic error classification:

try {
    $client->messages->create(...);
} catch (\Anthropic\Core\Exceptions\APIStatusException $e) {
    echo $e->type?->value;  // "rate_limit_error", "overloaded_error", etc.
}