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.
Architecture fit. The package is a thin, opinionated adapter sitting between an OpenAPI document and Laravel's artisan console kernel. It does not own HTTP transport (it wraps Laravel's HTTP client), does not own the spec (it reads local or remote YAML/JSON), and does not own the command surface (it registers commands dynamically from the spec). That is a good architectural shape for us: it is additive, sits at the edge of the app, and can be removed without touching domain code. The one structural commitment it forces is that the OpenAPI spec becomes a runtime dependency of the console kernel — a malformed or unreachable spec at boot time is a console-wide failure mode, not a single-command failure. Registration happens through a facade (OpenApiCli::register(...)) in a service provider, so we control when and how it loads.
Integration feasibility. High for a Laravel shop. The API is a fluent builder: baseUrl(), auth(fn () => ...), banner(), cache(ttl: 600), followRedirects(), yamlOutput(), showHtmlBody(), useOperationIds(), onError(...), retryOn(..., maxRetries: 1). Nothing exotic is required — no DI rewiring, no queue, no migrations, no schema changes. auth() and retryOn() accept closures, so existing token-manager services (OAuth refresh, Vault, AWS Secrets Manager) plug in without a bespoke adapter. The AuthenticationException added in 1.4.0 is the piece that makes this production-shaped rather than demo-shaped: auth failures abort with a friendly message and optional hint before the request is sent, instead of surfacing as a raw 401. Estimated integration effort for a first spec: 0.5–1 engineer-day including auth wiring; each additional spec is closer to hours.
Technical risk. The dominant risk is not code quality — it is adoption signal. 42 stars and 0 dependents means we would be among the first non-Spatie production users. The changelog shows a package still finding its edges: 1.1.0 fixed path-level parameter merging, 1.0.1 fixed query parameters with invalid characters, 1.3.1 fixed options silently dropping when a parameter description spans multiple lines, and 1.5.0 changed non-JSON and HTML body handling in a way the release notes themselves flag as breaking ("If you relied on the old notice appearing in output, that is the breaking bit"). That 1.5.0 change shipped as a minor release. For us that means: pin exact versions, read every changelog, and never let Composer float the package in CI. Secondary risks: derived command names can collide when different endpoints normalize to the same slug (e.g. /books/{id}/reviews and /books/{book_id}/reviews both landing on bookstore:get-books-reviews) — useOperationIds() is the mitigation and should be treated as mandatory, not optional. Spec drift is a third risk: a remote spec change silently changes our CLI surface on the next cache expiry (cache(ttl: 600) means ten minutes of surprise window), unless the spec is vendored.
Key questions.
auth() being a closure invoked per request, is the token cached anywhere, or do we pay a secrets-store round trip per command invocation?$ref resolves to an external document (cross-file refs), and what is the failure message when it can't?onError/retryOn do to exit codes and to --output-style machine-readable modes? Retry semantics matter if we build agents on top.Stack fit. Native Laravel 11/12/13 (13 support landed in 1.4.1), PHP 8.x, artisan-first. Two distinct deployment shapes, and we should pick one deliberately rather than drift:
For an internal developer tool, option 1 is fine. For anything agent-facing or externally consumed, option 2.
Migration path. There is no migration — this is greenfield console surface, not a replacement for existing code. The realistic "before" state is hand-rolled HTTP calls in scripts or Postman collections, and there is no forced cutover. Treat adoption as net-new and keep the old path alive until the new commands have been exercised against a non-production tenant.
Compatibility. Three pinning decisions, all cheap and all worth doing on day one:
"spatie/laravel-openapi-cli": "1.5.0") rather than a caret range, given the 1.5.0 precedent.cache() on but treat spec changes as a deploy event.useOperationIds(). Derived-from-path naming is unstable across spec edits and collision-prone.Sequencing.
useOperationIds(), JSON/YAML output, banner off. Answer the exit-code question empirically. Deliverable: a written verdict on whether the command surface is machine-consumable.auth() to the real token manager, throw AuthenticationException on missing credentials, add retryOn() for 401 and onError() for 429 with Retry-After surfaced to the user. This is where the package's value concentrates; a spec-to-CLI generator without auth handling is a toy.Explicit non-goal for phase 1: no agent integration. Prove the CLI is correct and stable before anything autonomous calls it.
Maintenance. Low in steady state, spiky around releases. Steady state is: spec changes require a re-pin, a re-vendor, and a smoke test of the affected commands. Spiky state is a Spatie minor release changing output behavior. Budget roughly 2–4 hours per dependency bump for verification, and treat any changelog entry mentioning output formatting as a potential breaking change regardless of the version number. The package itself is a thin layer — EndpointResolver was extracted in 1.2.0 and the codebase reads as small and single-purpose — so there is a credible fork-or-patch escape hatch if Spatie deprioritizes it. That escape hatch is the main reason the 0-dependents risk is tolerable rather than disqualifying.
Support. No commercial SLA. Spatie is a well-known, long-running agency with a paid-products business and a public security policy, but this specific package is community-supported. Internal ownership must be explicit: one named engineer owns the spec-to-command surface, the pinned version, and the smoke tests. Do not let this become everyone's tool and no one's responsibility — that is how pinned versions rot.
Scaling. Scales fine on the dimensions that matter here — number of specs, number of endpoints, command count. It does not scale on the dimension that bites: invocation volume. Every command invocation is a cold PHP process, re-reads or re-fetches the spec (mitigated by cache(ttl: 600)), resolves auth, and issues one HTTP call. That is acceptable for humans and for a handful of agent calls per minute. It is not acceptable as a high-throughput integration path — if a workflow needs thousands of API calls per hour, this is the wrong tool and we should say so early. Also watch artifact size for Laravel Zero builds that bundle large specs.
Failure modes. Ranked by likelihood and blast radius:
useOperationIds(); verify the registered command list in a test.retryOn() and AuthenticationException; test the refresh path, not just the happy path.Response is not JSON notice leaking into report.pdf). Re-verify on every bump if we expose download endpoints.onError() reading Retry-After and hard-capping maxRetries at 1.Ramp-up. Near-zero for anyone who already writes artisan commands — the mental model is "each endpoint is a command, each parameter is an option." Realistic ramp: half a day for a Laravel developer to be productive, mostly spent on auth wiring rather than the package. Two things need to be documented internally before rollout, because neither is discoverable from the README: the exit-code/output contract (what a caller can rely on), and the version-pinning and bump-verification procedure. Without the first, nobody can safely automate against it. Without the second, the first breaks quietly.
How can I help you explore Laravel packages today?