- PHP 100%
These LithiumHosting/* packages are standalone Laravel libraries licensed under MIT, not LiBilling-specific components. The "LiBilling is (C) Lithium Holdings..." preamble belongs only on LiBilling/* integration packages. Strip the preamble; keep the per-package MIT attribution line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| config | ||
| src | ||
| composer.json | ||
| LICENSE.md | ||
| README.md | ||
LiBilling Anthropic API Client
A typed PHP client for the Anthropic Messages API. Generic, knows nothing about LiBilling concepts. Used by libilling-ai-anthropic (and reusable by any Laravel application that needs to talk to Claude) to handle HTTP, error mapping, model pricing, and tool-use plumbing.
Features
- Synchronous Messages API:
POST /v1/messageswith full payload control (system, messages, tools, tool_choice) - Normalized response:
AnthropicResponseDTO with pre-extractedtool_useblocks and usage tokens - Typed exceptions: separate classes for rate-limit, timeout, 5xx server error, 4xx invalid request, content filter, and not-configured
- Fluent builders:
withApiKey(),withVersion(),withTimeout(),withEndpoint()for per-call overrides - Model catalog: per-million-token pricing in micros for Sonnet, Haiku, and Opus families;
extend()to register additional models at runtime - Container-bound: single
AnthropicClientresolves out of the Laravel container as a singleton
Installation
This package is loaded as a local Composer path repository. No separate installation is needed when developing within the LiBilling monorepo.
For standalone installation:
composer require lithiumhosting/laravel-anthropic-api
Configuration
Publish the config file:
php artisan vendor:publish --tag=laravel-anthropic-api-config
Config Options
| Key | Env | Default | Description |
|---|---|---|---|
api_key |
ANTHROPIC_API_KEY |
(unset) | API key. Callers may also pass an explicit key via AnthropicClient::withApiKey(). |
version |
ANTHROPIC_VERSION |
2023-06-01 |
Anthropic API version header |
model |
ANTHROPIC_MODEL |
claude-sonnet-4-6 |
Default model when the caller does not supply one |
timeout_seconds |
ANTHROPIC_TIMEOUT_SECONDS |
60 |
Request timeout |
max_tokens |
ANTHROPIC_MAX_TOKENS |
2000 |
Default max_tokens for outgoing payloads |
endpoint |
ANTHROPIC_ENDPOINT |
https://api.anthropic.com/v1/messages |
Override only for testing or a self-hosted gateway |
Usage
Resolve the singleton client and call messages() with a Messages API payload:
use LithiumHosting\AnthropicApi\AnthropicClient;
$response = app(AnthropicClient::class)->messages([
'system' => 'You are a helpful assistant.',
'messages' => [
['role' => 'user', 'content' => 'Hi.'],
],
]);
$response->id; // 'msg_01...'
$response->stopReason; // 'end_turn'
$response->inputTokens; // 12
$response->outputTokens; // 8
$response->latencyMs; // wall-clock latency
$response->raw; // full decoded body
Tool use
Forced tool use is supported by passing tools and tool_choice through. The AnthropicResponse::toolUseNamed() helper pulls the matching block out for you:
$response = app(AnthropicClient::class)->messages([
'system' => '...',
'messages' => [...],
'tools' => [[
'name' => 'submit_summary',
'description' => '...',
'input_schema' => [/* JSON Schema */],
]],
'tool_choice' => ['type' => 'tool', 'name' => 'submit_summary'],
]);
$tool = $response->toolUseNamed('submit_summary');
if ($tool !== null) {
$args = $tool['input']; // typed array of the tool arguments
}
Per-call overrides
The fluent builders return a clone, so the singleton stays untouched:
$shortLeash = app(AnthropicClient::class)
->withTimeout(15)
->withApiKey($externalCustomerKey);
$shortLeash->messages([...]);
Error handling
Every error path throws a typed exception. Catch the broadest base class for "any failure":
use LithiumHosting\AnthropicApi\Exceptions\AnthropicException;
use LithiumHosting\AnthropicApi\Exceptions\AnthropicRateLimitException;
use LithiumHosting\AnthropicApi\Exceptions\AnthropicContentFilteredException;
try {
$response = app(AnthropicClient::class)->messages($payload);
} catch (AnthropicRateLimitException $e) {
sleep($e->retryAfterSeconds ?? 5);
} catch (AnthropicContentFilteredException $e) {
// Model declined the request, distinct from integration failure.
} catch (AnthropicException $e) {
// Catch-all, timeout, 5xx, 4xx invalid, not configured.
}
Model catalog
Per-million-token pricing is stored in micros (millionths of a dollar) so cost math stays in integers:
use LithiumHosting\AnthropicApi\AnthropicModelCatalog;
AnthropicModelCatalog::has('claude-sonnet-4-6'); // true
AnthropicModelCatalog::inputMicrosPerMillion('claude-sonnet-4-6'); // 3_000_000
AnthropicModelCatalog::outputMicrosPerMillion('claude-sonnet-4-6'); // 15_000_000
// Register a preview model at runtime without modifying this package:
AnthropicModelCatalog::extend(
model: 'claude-foo-preview',
inputMicrosPerMillion: 5_000_000,
outputMicrosPerMillion: 25_000_000,
maxContextTokens: 200_000,
);
Dependencies
illuminate/support: Service provider, config mergingilluminate/http:Http::facade for the underlying client
Testing
vendor/bin/sail artisan test --filter=AnthropicClientTest
License
This package, laravel-anthropic-api is licensed under The MIT License (MIT). Please see License File for more information.
Is it any good?
Yes.
When people first hear about a new product, they frequently ask if it is any good. A Hacker News user remarked:
Note to self: Starting immediately, all raganwald projects will have a "Is it any good?" section in the readme, and the answer shall be "yes.".