apisutra / php
API Core SDK for declarative API clients
Requires
- php: ^8.4
- guzzlehttp/promises: ^2.0
- guzzlehttp/psr7: ^2.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0
- psr/log: ^3.0
- psr/simple-cache: ^3.0
- revolt/event-loop: ^1.0.9
Requires (Dev)
- guzzlehttp/guzzle: ^7.0
- illuminate/container: ^13.0
- illuminate/translation: ^13.0
- illuminate/validation: ^13.0
- pestphp/pest: ^3.8
- phpstan/phpstan: ^2.1
- squizlabs/php_codesniffer: ^4.0
Suggests
- ext-redis: Optional atomic shared rate limits (phpredis >=6.2 with Redis >=7.0)
- apisutra/laravel: Laravel 13 integration: package discovery, dependency injection and HTTP adapters
Provides
None
Conflicts
None
Replaces
None
README
ApiSutra
Declarative PHP SDK for external APIs
ApiSutra is a PHP toolkit for external API SDKs: request and DTO declarations, authentication and execution policies, typed results and diagnostics. PHP 8.4+. Works standalone; Laravel 13 integration is a separate package.
Quickstart · Capabilities · Documentation
Install
composer require apisutra/php
Run the example
The included Records SDK uses mock responses: no API keys or network access needed.
php vendor/apisutra/php/docs/example/sdk/run.php
From a request to a DTO
These snippets use the Records SDK; imports are omitted, the API address and token are illustrative.
Declare the operation
#[Get('/records/{id}')] #[Retry(attempts: 3)] #[Returns(GetRecordResponseDto::class, unwrap: 'data')] final class GetRecordRequest extends AbstractRequest { public function __construct(#[Path] public int $id) {} }
The request declares its route, up to three attempts, and the response DTO.
Describe the data
A shortened version of the response DTO:
final readonly class GetRecordResponseDto extends AbstractDto { public function __construct( #[From('record_id', fallback: ['id'])] public int $id, public string $title, #[From('created_at')] #[DateTimeFrom(format: DATE_ATOM)] public DateTimeImmutable $createdAt, ) {} }
Explore a detailed DTO example with attributes: mapping, casts, nested DTOs, collections, extras and files. Custom hydrators are supported; toArray() and HTTP serialization have separate rules.
Configure the client and send
Only baseUrl is required in ClientConfig. Add the policies your integration needs, for example:
$config = new ClientConfig( baseUrl: 'https://api.example.test', auth: new BearerAuthenticator('your-api-token'), timeout: 15, retry: new RetryConfig(attempts: 3, totalTimeoutMs: 30_000), hydration: new HydrationConfig(policy: new RulePolicy(scalars: ScalarPolicy::Strict)), throwOnErrors: false, ); $client = new DemoClient($config, HttpTransport::createDefault()); $handle = $client->send($client->records()->get(7)->withTimeout(5)); /** @var GetRecordResponseDto $record */ $record = $handle->dataOrFail(); echo $record->createdAt->format('Y-m-d');
The client allows 15 seconds per HTTP attempt and 30 seconds overall; this request's withTimeout(5) sets 5 seconds per attempt. $config->with(...) creates a new configuration. Other settings: cache, quotas, logging, serialization and extensions.
send() waits and returns a ResultHandle. With throwOnErrors: false, you can inspect failures before deciding how to handle them:
| Read the handle | Value and purpose |
|---|---|
dataOrFail() |
The declared DTO for application logic; throws on FAILED even with throwOnErrors: false. Other requests can return collections, arrays, scalars, text, null or FileResponse. |
resolved() |
ResolvedResultInterface: data, status, messages and mapped errors for application branching or UI. Inspect FAILED without throwing. |
raw() |
ExecutionResult: original HTTP response, errors, metadata, child results, trace/audit/debug for diagnostics and custom processing. It does not select an undecoded body. |
All three read the same execution without another HTTP call. Result representations and errors →
Independent calls and large datasets
Start independent requests before waiting to overlap HTTP with the built-in Guzzle transport:
$first = $client->sendAsync($client->records()->get(7)); $second = $client->sendAsync($client->records()->get(8)); $record7 = $first->wait()->dataOrFail(); $record8 = $second->wait()->dataOrFail();
sendAsync() returns a typed Guzzle-compatible promise. Await it; no worker or manual event-loop setup is needed.
Here $request is a paginated request bound to a client, and $repository is application storage:
foreach ($request->paginate()->items() as $item) { $repository->save($item); }
The item stream loads pages sequentially without retaining the dataset; FAILED throws. For collection and concurrency, see pagination; for incremental processing of independent requests, see pool consume().
Capability map
Organize an SDK
| Need | What ApiSutra provides |
|---|---|
| SDK structure | Clients and nested resources, multiple services, API versions, and client discovery. |
| SDK catalogs | Operation inventories, response DTO catalogs, and static provider reference data: capabilities, tariffs, and dictionaries without HTTP. |
Configure clients, transport, and authentication
| Need | What ApiSutra provides |
|---|---|
| Configuration | Client settings, copies and per-call overrides, optional container integration, multilingual messages with a per-client locale and custom translations. |
| HTTP and async | PSR-18/PSR-17 transport integration, synchronous send(), concurrent sendAsync(), cancellation; typed Guzzle-compatible promises with wait/then/otherwise. Custom transports must support async explicitly. |
| Authentication and tokens | API key, Bearer, Basic, HMAC and auth scopes; token caching, refresh after 401 and refresh locks. Shared storage alone does not guarantee cross-process locking. |
| OAuth2 | Client Credentials and Authorization Code with PKCE S256, state validation, automatic refresh, effective scopes, and export/restore of tokens and authorization attempts. Storage and cross-process rotation coordination belong to the application. |
| Credentials and destinations | Credential enrichment and origin protection, isolated authentication contexts; complete and signed URLs without automatically forwarding client credentials. |
Describe requests and outgoing data
| Need | What ApiSutra provides |
|---|---|
| Request declarations | HTTP attributes, path/query/header/body fields, oneOf and discriminator; request validation before HTTP, custom preflight checks and explicit DTO validation. Validate rules require Illuminate Validation. |
| Serialization | Separate DTO toArray() rules and HTTP naming, array and boolean formats; dates, enums, JSON/forms, JSON within a field, root bodies for JSON Patch/bulk. |
| Response formats | DTOs with explicit unwrap, or RawResponse; JSON arrays, scalars, null and text without a DTO. raw() reads execution details; RawResponse selects an undecoded body. |
| Files and archives | Streaming multipart/binary uploads and Base64, DTO file fields, downloads to files or streams, listing, reading and extracting archives. Base64 materializes the contents. |
| Transfer progress | Opt-in upload/download byte counters for sync/async, separate attempts, trace and unknown totals. |
Transform responses and DTOs
Control execution, load, and caching
| Need | What ApiSutra provides |
|---|---|
| Safe retries | Retry policies, backoff, Retry-After and idempotency; per-call delay overrides via withRetryDelay() and replay checks for file operations. |
| Time limits | Per-attempt timeouts, total execution budgets and shared deadlines across retries, authentication and dependent calls. |
| Request quotas | Joint client and operation quotas, waiting or refusal; local accounting or an optional atomic Redis backend. |
| Server cooldown | Coordinate Retry-After prohibitions after 429 by operation/group, origin and credentials; budget-aware waits or refusal, optional sharing across clients/processes. |
| Response caching | PSR-16, TTL, per-call modes, clearing and SDK/credential isolation. HTTP caching and token storage have separate controls. |
Coordinate calls and large datasets
| Need | What ApiSutra provides |
|---|---|
| Batch and pool | Sequential/concurrent batch, concurrent pool, concurrency limits and failure strategies; collected results and child diagnostics. |
| Incremental processing | Pool consume() / consumeAsync(): iterable input of unknown size, handlers and summary counters without retaining all results; optional stop on failure. |
| Pagination | Page/offset/cursor schemas, typed items, DTO metadata containers and traversal guards; lazy pages/items, ordered concurrent collection of independent pages and a shared deadline. Cursors stay sequential; concurrent all() needs total/perPage; aggregate string-key collisions fail explicitly. |
| Dependent operations | Composite requests and dependencies between steps, composed results and a shared execution budget. |
| Deferred provider results | Pending/Ready criteria, continuation tokens, await and bounded polling. This concerns provider operation readiness, separately from concurrent HTTP. |
Read results and diagnose failures
| Need | What ApiSutra provides |
|---|---|
| Results and errors | Handle, application view and full execution result; SUCCESS/PARTIAL/FAILED, result or exception delivery and provider error mapping, exception factories and custom result methods. |
| Tracing and diagnostics | Sync/async call trees, correlated logs, audit, debug and secret masking; machine reasons/stages and DTO error paths with original source locations. |
| Observation | Safe execution snapshots: operation, outcome, correlation, attempt counts and durations, optional attempt details and a diagnostic client label. Delivery belongs to the application or Laravel adapter. |
Extend, test, and scaffold
| Need | What ApiSutra provides |
|---|---|
| Extension points | Lifecycle hooks, modules, response-format and attribute handlers, custom auth/casts/hydration, transport and executor decorators. |
| SDK testing | Fakes, dynamic/file responses, sequences, sent assertions, missing-mock checks and reversible sessions; record/playback and live-testing helpers. |
| Scaffolding | CLI generators for clients, requests and DTOs in the project's namespace; runnable examples and the Records SDK. |
Laravel 13
apisutra/laravel connects the same SDK to Laravel:
- Application integration: discovery, client/request DI, application defaults, validation, HTTP input mapping and controller responses.
- Collections:
Pagination::collect()wraps the lazy item stream inLazyCollection. - Testing:
ApiSutra::for($client)->fake()with isolated responses, assertions and automatic missing-mock checks. - Observation: execution events, optional Telescope integration and a selected log channel.
- Queues and tooling: middleware to release repeatable jobs on SDK throttling, Artisan generators and
artisan about.
The adapter installs the core too. Use an SDK in Laravel · Add Laravel support to your SDK.
Choose your next step
| Your task | Start here |
|---|---|
| Build an SDK for an API | Create an SDK |
| Use an existing SDK in an application | Use an SDK |
| Try features locally | Runnable examples |
| Check settings, behavior or limitations | Reference |
| Work with an AI coding agent | Usage instructions and capability map |
