Search by

API Core SDK for declarative API clients

Package info

github.com/apisutra/php

pkg:composer/apisutra/php

Statistics

Installs: 56

Dependents: 1

Suggesters: 0

Stars: 0

Open Issues: 0

v0.4.0 2026-10-01 11:46 UTC

This package is auto-updated.

Last update: 2026-10-01 11:48:21 UTC


README

ApiSutra logo

ApiSutra

Declarative PHP SDK for external APIs

Tests Test count Docs CI Multilingual documentation PHP 8.4+ Packagist MIT license

English · Русский

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

Need What ApiSutra provides
Model declarations Attributes on ordinary and readonly classes, optional base DTOs, external rules without changing models, inheritance and recursive models.
Field mapping Input names, nested paths and fallbacks; independent output names, hydration profiles and shared policy.
Field contracts Missing vs null, required presence, forbidden null, defaults and empty strings; checking constructor-assigned values.
Types and precision Scalar conversions, opt-in Strict, unions and large integer IDs without lost digits; enums, date formats and time zones.
Complex structures Nested DTOs, strict list shapes and JSON object/array validation, typed collections, type-level variants and typed fallback.
Incoming JSON / webhooks Public JSON → DTO hydration with the same shape policy as HTTP; check a dictionary before a cast.
Additional data Preserve unmapped input with Extras, retain it in toArray(), and exclude the receiver from outgoing requests.
Custom transformations Input/output casts and nested transformations with context, including without HTTP, computed values; custom hydrators with DI and native fallback.

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 in LazyCollection.
  • 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

Contributing

Development guide · Agent instructions · Contributing.

Changelog · MIT license.