Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Api Client Laravel Package

mittwald/api-client

PHP client for the mittwald API, generated from its OpenAPI specification. It offers typed request and response models plus PSR-18 HTTP integration, making it easy to automate mittwald hosting resources such as projects, apps, domains, and databases from PHP or Laravel.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a generated PHP SDK for the mittwald mStudio v2 API — think "manage hosting projects, apps, domains, databases, and cronjobs programmatically." It is a thin transport layer over REST, so the fastest path to productivity is to read the generated resource classes rather than the README.

  1. Install and pick a PSR-18 adapter. The client is transport-agnostic; you almost certainly need to bring your own HTTP client:
    composer require mittwald/api-client guzzlehttp/guzzle
    
  2. Get a token. Create an API token in the mStudio UI (Personal Access Token / API token under user settings). Scope it to the projects it needs — tokens carry the permissions of the creating user.
  3. First call — list your projects. This is the "hello world" because everything else hangs off a projectId:
    use Mittwald\ApiClient\Client;
    
    $client = new Client($token); // or new Client($token, $psr18Client)
    
    $projects = $client->project()->listProjects();
    
    Resource accessors are grouped by domain (project(), app(), domain(), database(), container(), user(), ...). If a method name is not obvious, open src/ and grep the endpoint path — the method names mirror the API docs closely.
  4. Wire it into Laravel once (see below) and never new Client() in a controller again.

First real use case: enumerate projects, then list the apps/installations inside one. That tells you whether the token's scopes and the ID shapes match your expectations before you build anything on top.

Implementation Patterns

Bind it as a scoped singleton with a configured PSR-18 client. Never let the SDK construct its own transport — you lose timeouts, retries, and observability.

// config/services.php
'mittwald' => [
    'token' => env('MITTWALD_API_TOKEN'),
    'base_uri' => env('MITTWALD_API_BASE_URI'), // for staging / mocking
    'timeout' => env('MITTWALD_TIMEOUT', 15),
],

// AppServiceProvider::register()
$this->app->singleton(Client::class, function ($app) {
    $http = new \GuzzleHttp\Client([
        'timeout' => config('services.mittwald.timeout'),
        'connect_timeout' => 5,
        'headers' => ['User-Agent' => 'my-app/1.0'],
    ]);
    return new Client(config('services.mittwald.token'), $http);
});

Wrap it in your own gateway, not in controllers. The generated API surface is wide and will churn; a single MittwaldGateway class isolates your domain vocabulary (provisionSite, attachDomain) from generated method names. This is the cheapest insurance against a minor version bump.

Treat every list endpoint as paginated. Generated clients typically expose limit/offset (or a cursor) as parameters on the request object. Write one helper and reuse it:

function all(callable $fetch, int $pageSize = 100): \Generator {
    $offset = 0;
    do {
        $batch = $fetch(['limit' => $pageSize, 'offset' => $offset]);
        yield from $batch;
        $offset += $pageSize;
    } while (count($batch) === $pageSize);
}

Model long-running operations as jobs. Provisioning, app installs, and domain changes are frequently asynchronous — you get an operation/installation ID back, not a finished resource. Poll with exponential backoff in a queued job, persist the operation ID, and make the job idempotent so retries don't double-provision:

class AwaitInstallation implements ShouldQueue {
    public $tries = 10;
    public $backoff = [5, 10, 30, 60];
    public function handle(MittwaldGateway $mw) {
        $op = $mw->getOperation($this->operationId);
        if ($op->isFinished()) { /* update local model */ }
        else { $this->release(15); }
    }
}

Route bulk work through the queue. The API is rate-limited per token; a foreach over 200 projects will get you 429s. Chunk with Bus::batch() and keep concurrency low (2–3) so your polling doesn't compete with user-triggered requests.

Test with a fake PSR-18 client, not by mocking the SDK. Because the transport is injected, you can drop in Http\Mock\Client and queue canned JSON responses. This exercises the real (de)serialization — which is where generated clients actually break.

$mock = new \Http\Mock\Client();
$mock->addResponse(new \GuzzleHttp\Psr7\Response(200, [], json_encode(['items' => []])));
$client = new Client('token', $mock);

Webhooks (if you use them) replace polling. For "app installed / domain verified" events, prefer the webhook to the polling loop — but verify the signature and return 2xx fast, then dispatch a job. Keep polling as a fallback reconciler, because webhook delivery is at-least-once, not exactly-once.

Gotchas and Tips

  • This is a 3-star, machine-generated client. Its value is fidelity to the spec, not API stability. Pin the exact version ("mittwald/api-client": "1.2.3", not ^1.0) and read the CHANGELOG before upgrading. Method signatures, constructor order, and DTO property names are the likely breaking points.
  • Constructors are not stable across generated clients. Verify whether your version is new Client($token) or new Client($token, $httpClient) or requires a config object — check src/Client.php first. Everything downstream depends on this.
  • You must supply a PSR-18 implementation. A bare composer require mittwald/api-client may not pull a transport; the failure mode is a confusing "no HTTP client found" error at runtime, not at install time.
  • No default timeouts. Guzzle defaults to no timeout; one hung request will pin a queue worker. Always set connect_timeout and timeout, and set a lower timeout for polling loops.
  • Error semantics: 403 vs 404. Tokens are scoped by project and user role. A 403 usually means the token lacks a scope, while a 404 often means "exists but you can't see it" — don't treat 404 as "safe to create." Check for existence with a list call scoped to a project you know you can read.
  • Exceptions carry the response body — log it. API error payloads usually include a machine-readable code and a human message. Catch the SDK's exception, log status, request ID (usually in a response header like X-Request-Id), and body, then rethrow a domain exception. That request ID is what mittwald support will ask for.
  • IDs are opaque strings. Don't cast to int, don't string-compare for ordering, don't build URLs by hand — always go through the client so base URI and version prefixes stay correct.
  • Money, sizes, and dates are often strings in generated DTOs. "9.99" not 9.99, ISO-8601 timestamps not DateTime. Convert at your boundary, once.
  • DTOs are effectively immutable value objects. Use getters; mutating a response object won't persist anything and may not even be possible with readonly properties.
  • 429 handling is your job. Respect Retry-After if present, otherwise exponential backoff with jitter. A naive retry storm on a shared token will get the whole integration throttled.
  • Extension point: HTTP middleware. The cleanest place for retries, logging, and request-ID capture is a HandlerStack middleware on the injected Guzzle client — not a decorator around the SDK. That keeps the SDK unmodified and upgradeable.
  • Never log the token. Generated clients sometimes dump request headers in exception messages; scrub Authorization in your logging middleware.
  • Base URI override is the mocking seam. Point MITTWALD_API_BASE_URI at a local stub (WireMock, or a Laravel Http::fake()-backed listener) for integration tests — far more reliable than mocking dozens of generated methods.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin