Laravel API Client for Anthropic
Find a file
Troy Siedsma 81a4c7fc6b README: drop the LiBilling commercial preamble from the License section
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>
2026-05-22 15:40:41 +00:00
config Re-platform onto LithiumHosting/laravel-anthropic-api Forgejo origin 2026-05-22 13:15:33 +00:00
src Re-platform onto LithiumHosting/laravel-anthropic-api Forgejo origin 2026-05-22 13:15:33 +00:00
composer.json Standardize composer.json + README + LICENSE 2026-05-22 15:03:44 +00:00
LICENSE.md LICENSE: Copyright (c) Lithium Holdings, LLC 2026-05-22 15:13:41 +00:00
README.md README: drop the LiBilling commercial preamble from the License section 2026-05-22 15:40:41 +00:00

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/messages with full payload control (system, messages, tools, tool_choice)
  • Normalized response: AnthropicResponse DTO with pre-extracted tool_use blocks 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 AnthropicClient resolves 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 merging
  • illuminate/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.".