Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Build LLM-powered apps with the Anthropic Claude API or SDK across Python, TypeScript, Java, Go, Ruby, C#, and PHP.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
php/claude-api.md
1# Claude API — PHP23> **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.45## Installation67```bash8composer require "anthropic-ai/sdk"9```1011## Client Initialization1213```php14use Anthropic\Client;1516// Using API key from environment variable17$client = new Client(apiKey: getenv("ANTHROPIC_API_KEY"));18```1920### Amazon Bedrock2122```php23use Anthropic\Bedrock;2425// Constructor is private — use the static factory. Reads AWS credentials from env.26$client = Bedrock\Client::fromEnvironment(region: 'us-east-1');27```2829### Google Vertex AI3031```php32use Anthropic\Vertex;3334// Constructor is private. Parameter is `location`, not `region`.35$client = Vertex\Client::fromEnvironment(36location: 'us-east5',37projectId: 'my-project-id',38);39```4041### Anthropic Foundry4243```php44use Anthropic\Foundry;4546// Constructor is private. baseUrl or resource is required.47$client = Foundry\Client::withCredentials(48authToken: getenv('ANTHROPIC_FOUNDRY_AUTH_TOKEN'),49baseUrl: 'https://<resource>.services.ai.azure.com/anthropic',50);51```5253---5455## Basic Message Request5657```php58$message = $client->messages->create(59model: 'claude-opus-4-7',60maxTokens: 16000,61messages: [62['role' => 'user', 'content' => 'What is the capital of France?'],63],64);6566// content is an array of polymorphic blocks (TextBlock, ToolUseBlock,67// ThinkingBlock). Accessing ->text on content[0] without checking the block68// type will throw if the first block is not a TextBlock (e.g., when extended69// thinking is enabled and a ThinkingBlock comes first). Always guard:70foreach ($message->content as $block) {71if ($block->type === 'text') {72echo $block->text;73}74}75```7677If you only want the first text block:7879```php80foreach ($message->content as $block) {81if ($block->type === 'text') {82echo $block->text;83break;84}85}86```8788---8990## Streaming9192> **Requires SDK v0.5.0+.** v0.4.0 and earlier used a single `$params` array; calling with named parameters throws `Unknown named parameter $model`. Upgrade: `composer require "anthropic-ai/sdk:^0.7"`9394```php95use Anthropic\Messages\RawContentBlockDeltaEvent;96use Anthropic\Messages\TextDelta;9798$stream = $client->messages->createStream(99model: 'claude-opus-4-7',100maxTokens: 64000,101messages: [102['role' => 'user', 'content' => 'Write a haiku'],103],104);105106foreach ($stream as $event) {107if ($event instanceof RawContentBlockDeltaEvent && $event->delta instanceof TextDelta) {108echo $event->delta->text;109}110}111```112113---114115## Tool Use116117### Tool Runner (Beta)118119**Beta:** The PHP SDK provides a tool runner via `$client->beta->messages->toolRunner()`. Define tools with `BetaRunnableTool` — a definition array plus a `run` closure:120121```php122use Anthropic\Lib\Tools\BetaRunnableTool;123124$weatherTool = new BetaRunnableTool(125definition: [126'name' => 'get_weather',127'description' => 'Get the current weather for a location.',128'input_schema' => [129'type' => 'object',130'properties' => [131'location' => ['type' => 'string', 'description' => 'City and state'],132],133'required' => ['location'],134],135],136run: function (array $input): string {137return "The weather in {$input['location']} is sunny and 72°F.";138},139);140141$runner = $client->beta->messages->toolRunner(142maxTokens: 16000,143messages: [['role' => 'user', 'content' => 'What is the weather in Paris?']],144model: 'claude-opus-4-7',145tools: [$weatherTool],146);147148foreach ($runner as $message) {149foreach ($message->content as $block) {150if ($block->type === 'text') {151echo $block->text;152}153}154}155```156157### Manual Loop158159Tools are passed as arrays. **The SDK uses camelCase keys** (`inputSchema`, `toolUseID`, `stopReason`) and auto-maps to the API's snake_case on the wire — since v0.5.0. See [shared tool use concepts](../shared/tool-use-concepts.md) for the loop pattern.160161```php162use Anthropic\Messages\ToolUseBlock;163164$tools = [165[166'name' => 'get_weather',167'description' => 'Get the current weather in a given location',168'inputSchema' => [ // camelCase, not input_schema169'type' => 'object',170'properties' => [171'location' => ['type' => 'string', 'description' => 'City and state'],172],173'required' => ['location'],174],175],176];177178$messages = [['role' => 'user', 'content' => 'What is the weather in SF?']];179180$response = $client->messages->create(181model: 'claude-opus-4-7',182maxTokens: 16000,183tools: $tools,184messages: $messages,185);186187while ($response->stopReason === 'tool_use') { // camelCase property188$toolResults = [];189foreach ($response->content as $block) {190if ($block instanceof ToolUseBlock) {191// $block->name : string — tool name to dispatch on192// $block->input : array<string,mixed> — parsed JSON input193// $block->id : string — pass back as toolUseID194$result = executeYourTool($block->name, $block->input);195$toolResults[] = [196'type' => 'tool_result',197'toolUseID' => $block->id, // camelCase, not tool_use_id198'content' => $result,199];200}201}202203// Append assistant turn + user turn with tool results204$messages[] = ['role' => 'assistant', 'content' => $response->content];205$messages[] = ['role' => 'user', 'content' => $toolResults];206207$response = $client->messages->create(208model: 'claude-opus-4-7',209maxTokens: 16000,210tools: $tools,211messages: $messages,212);213}214215// Final text response216foreach ($response->content as $block) {217if ($block->type === 'text') {218echo $block->text;219}220}221```222223`$block->type === 'tool_use'` also works; `instanceof ToolUseBlock` narrows for PHPStan.224225226---227228## Extended Thinking229230**Adaptive thinking is the recommended mode for Claude 4.6+ models.** Claude decides dynamically when and how much to think.231232```php233use Anthropic\Messages\ThinkingBlock;234235$message = $client->messages->create(236model: 'claude-opus-4-7',237maxTokens: 16000,238thinking: ['type' => 'adaptive'],239messages: [240['role' => 'user', 'content' => 'Solve: 27 * 453'],241],242);243244// ThinkingBlock(s) precede TextBlock in content245foreach ($message->content as $block) {246if ($block instanceof ThinkingBlock) {247echo "Thinking:\n{$block->thinking}\n\n";248// $block->signature is an opaque string — preserve verbatim if249// passing thinking blocks back in multi-turn conversations250} elseif ($block->type === 'text') {251echo "Answer: {$block->text}\n";252}253}254```255256> **Deprecated:** `['type' => 'enabled', 'budgetTokens' => N]` (fixed-budget extended thinking) still works on Claude 4.6 but is deprecated. Use adaptive thinking above.257258`$block->type === 'thinking'` also works for the check; `instanceof` narrows for PHPStan.259260---261262## Prompt Caching263264`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`.265266```php267$message = $client->messages->create(268model: 'claude-opus-4-7',269maxTokens: 16000,270system: [271['type' => 'text', 'text' => $longSystemPrompt, 'cacheControl' => ['type' => 'ephemeral']],272],273messages: [['role' => 'user', 'content' => 'Summarize the key points']],274);275```276277For 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.278279Verify hits via `$message->usage->cacheCreationInputTokens` / `$message->usage->cacheReadInputTokens`.280281---282283## Structured Outputs284285### Using StructuredOutputModel (Recommended)286287Define a PHP class implementing `StructuredOutputModel` and pass it as `outputConfig`:288289```php290use Anthropic\Lib\Contracts\StructuredOutputModel;291use Anthropic\Lib\Concerns\StructuredOutputModelTrait;292use Anthropic\Lib\Attributes\Constrained;293294class Person implements StructuredOutputModel295{296use StructuredOutputModelTrait;297298#[Constrained(description: 'Full name')]299public string $name;300301public int $age;302303public ?string $email = null; // nullable = optional field304}305306$message = $client->messages->create(307model: 'claude-opus-4-7',308maxTokens: 16000,309messages: [['role' => 'user', 'content' => 'Generate a profile for Alice, age 30']],310outputConfig: ['format' => Person::class],311);312313$person = $message->parsedOutput(); // Person instance314echo $person->name;315```316317Types are inferred from PHP type hints. Use `#[Constrained(description: '...')]` to add descriptions. Nullable properties (`?string`) become optional fields.318319### Raw Schema320321```php322$message = $client->messages->create(323model: 'claude-opus-4-7',324maxTokens: 16000,325messages: [['role' => 'user', 'content' => 'Extract: John ([email protected]), Enterprise plan']],326outputConfig: [327'format' => [328'type' => 'json_schema',329'schema' => [330'type' => 'object',331'properties' => [332'name' => ['type' => 'string'],333'email' => ['type' => 'string'],334'plan' => ['type' => 'string'],335],336'required' => ['name', 'email', 'plan'],337'additionalProperties' => false,338],339],340],341);342343// First text block contains valid JSON344foreach ($message->content as $block) {345if ($block->type === 'text') {346$data = json_decode($block->text, true);347break;348}349}350```351352---353354## Beta Features & Server-Side Tools355356**`betas:` is NOT a param on `$client->messages->create()`** — it only exists on the beta namespace. Use it for features that need an explicit opt-in header:357358```php359use Anthropic\Beta\Messages\BetaRequestMCPServerURLDefinition;360361$response = $client->beta->messages->create(362model: 'claude-opus-4-7',363maxTokens: 16000,364mcpServers: [365BetaRequestMCPServerURLDefinition::with(366name: 'my-server',367url: 'https://example.com/mcp',368),369],370betas: ['mcp-client-2025-11-20'], // only valid on ->beta->messages371messages: [['role' => 'user', 'content' => 'Use the MCP tools']],372);373```374375**Server-side tools** (bash, web_search, text_editor, code_execution) are GA and work on both paths — `Anthropic\Messages\ToolBash20250124` / `WebSearchTool20260209` / `ToolTextEditor20250728` / `CodeExecutionTool20260120` for non-beta, `Anthropic\Beta\Messages\BetaToolBash20250124` / `BetaWebSearchTool20260209` / `BetaToolTextEditor20250728` / `BetaCodeExecutionTool20260120` for beta. No `betas:` header needed for these.376