spatie/laravel-openapi-cli
Turn any OpenAPI spec into dedicated Laravel artisan commands with typed options for path params, query params, and request bodies. Add auth, caching, retries, and error handling, or pair with Laravel Zero to build standalone CLI tools.
bookstore:get-books, bookstore:post-books, bookstore:get-books-reviews, plus a bookstore:list index — but the hidden scope is large and unglamorous: resolving $refs, merging path-level parameters into operation parameters (1.1.0), mapping path/query/body fields to typed artisan options, preserving options when a parameter description spans multiple lines (1.3.1), encoding query parameters with awkward characters (1.0.1), and writing non-JSON bodies through untouched so php artisan api:download-report > report.pdf yields a valid PDF (1.5.0). Adopting means that bug class lives on Spatie's backlog instead of ours. Declining is a real commitment, not a savings.--use-operation-ids mode so generated command names don't drift when a path is renamed. If our roadmap includes letting an agent (or an internal automation) drive third-party APIs, this is the cheapest credible substrate — the tool surface is the spec, so coverage tracks the vendor's own contract rather than our hand-written wrappers.baseUrl, auth callback, banner, TTL cache, redirect following). That makes "should we integrate with vendor X's API?" a cheap experiment: stand up a CLI, exercise the endpoints, decide. The cache(ttl: 600) option further means exploratory use doesn't hammer a partner's rate limits or burn quota during evaluation.auth() for token injection, retryOn() to refresh an OAuth token on a 401 and retry once, AuthenticationException (1.4.0) to abort before a request with a friendly message and optional hint, and onError() to turn a 429 into a human-readable "rate limited, retry after Ns" instead of a stack trace. For PMs, this is the difference between a tool support will actually use and one that generates tickets.php artisan command inside our monolith today and be extracted into a distributable Phar later. That's a phased-delivery option we don't have to pre-commit to up front.Adopt when most of these hold:
EndpointResolver for deduplication (1.2.0), and Laravel 13 support landed (1.4.1). The changelog reads like a team responding to real bug reports, which is the signal that matters more than star count here.Look elsewhere when any of these hold:
maxRetries: 1 in the documented example), not a backoff policy engine.One caveat worth writing into the decision record: the 1.5.0 release note calls out a behavioral break — the old Response is not JSON (...) notice no longer appears. Any script of ours that grepped for that string breaks. If we're going to depend on output format, we need to treat stdout as a semi-stable interface and pin versions.
For executives — one paragraph. A vendor we already trust for Laravel infrastructure has packaged the boring part of API integration: point it at an API's public specification and it generates a working, typed command-line interface for every endpoint, complete with auth, retry-on-401 token refresh, rate-limit messaging, response caching, and clean binary/file download output. The strategic value is twofold. First, it turns "should we integrate with partner X?" from a multi-sprint build into an afternoon's experiment. Second, it gives our internal tooling and any AI-agent workflows a ready-made, spec-accurate way to call external APIs — the agent's capability surface stays in sync with the vendor's contract for free. Cost is zero (MIT), effort is days not quarters, and the downside is bounded: we can vendor it if the maintainer walks away. The one honest risk is that it's early — 42 stars, no downstream dependents — so we should treat it as an internal-productivity tool, not load-bearing production infrastructure, until it has more mileage.
For engineering — the specifics. It's spatie/laravel-openapi-cli, MIT, actively released (1.5.0 as of mid-2026, Laravel 13 supported). Registration is fluent: OpenApiCli::register($specUrl, 'bookstore')->baseUrl(...)->auth(fn () => ...)->cache(ttl: 600)->useOperationIds()->onError(...)->retryOn(...). Each endpoint becomes an artisan command — bookstore:get-books, bookstore:delete-books — with typed options for path params, query params, and request bodies; bookstore:list enumerates everything. It handles the fiddly parts we'd otherwise write and re-write: $ref resolution, path-level parameter merging, multi-line parameter descriptions, query-string encoding, OAuth 401 refresh-and-retry, and raw passthrough of non-JSON bodies so redirecting to a file produces valid bytes. It works both inside our app and as a standalone Laravel Zero Phar, so we can prototype in-app and extract later without a rewrite. Known constraints to design around: it's a CLI surface, not an in-app typed client; retries are single-shot, not a backoff engine; and stdout formatting changed in 1.5.0, so pin the version and don't build parsers on ad-hoc output strings. Suggested first step: a one-week spike against our highest-friction partner API, measured by "how many endpoints can an on-call engineer exercise without reading the vendor's docs."
How can I help you explore Laravel packages today?