Integrating Scramble with Cachet - the open source stability platform
February 27, 2025
My experience of integrating Scramble into an existing codebase.
Recently, I got a chance to work on integrating Scramble with Cachet.
Cachet is an open-source stability platform. The Cachet API implements the JSON:API standard, uses Spatie’s Query Builder, and relies on Laravel Data for its implementation. This makes Scramble a perfect solution for generating API documentation that is always up-to-date.
Scramble is a modern Laravel OpenAPI (Swagger) documentation generator that doesn’t require you to write PHPDoc annotations: https://scramble.dedoc.co.
When I started working on the Cachet and Scramble integration, a few pieces were missing:
- Manually documenting parameters not directly referenced in a controller method
- JSON:API support
- Grouping and sorting endpoint groups
Deploying the generated specification was also an interesting challenge. Cachet documentation lives in a separate repository, and the OpenAPI specification file is located there as well. So generating the spec via CI was going to be fun!
First steps
My first step was to install Scramble and see the initial results. Usually, API documentation is mostly ready right after installation. After installing the packages, requests and endpoints were correctly detected.
Cachet uses Laravel Data objects for requests and Query Builder for building API queries, so Scramble handled the request documentation perfectly!
However, I noticed that responses were completely missing. Endpoint groups, as the maintainers had planned, were also missing.
The most important part, of course, was the responses. While scanning the codebase manually, it looked like these were just Laravel API responses. In reality, they were JSON:API resources from Tim MacDonald’s package. To make this work for Cachet, I needed to add JSON:API support, which had already been on my Scramble roadmap for a while.
Implementing JSON:API support
JSON:API
is awesome: https://jsonapi.org/format/
It defines a solid API foundation and removes the need to make countless decisions about API structure.
Tim MacDonald’s package provides a great Laravel implementation of JSON:API
, and combined with Laravel Query Builder, it’s probably the best way to build an API.
To document the Cachet API, I needed to implement support for JSON:API
responses: essentially mapping JSON:API
resource types to Response
objects in the OpenAPI specification. For the first iteration, I focused on supporting attributes and relationships — enough to get Cachet documented.
In the future, I’ll also need to add support for documenting request parameters that are implicitly available when using JSON:API
resources, but even now, the responses are communicated clearly:
Early JSON:API
support is available in Scramble PRO 0.7.x
.
Manual parameters documentation and groups
When I started working on Scramble integration with Cachet, there were already comments in the codebase for API documentation. While Scramble can document route parameters using PHPDoc annotations placed near their origin AST node, I realized that introducing PHP attributes would provide a better experience.
Attributes offer a simpler, more explicit way to define type, default, example, description, etc. Attributes also allow adding parameters to the documentation even if there’s no corresponding AST node in the controller method (for example, request parameters related to pagination).
With that in mind, I implemented PHP attributes for defining request parameters:
Cachet endpoints were also grouped and ordered in a specific way. While Scramble already supported grouping, it lacked sorting functionality. So I introduced the #[Group]
attribute, which supports both sorting groups and explicitly grouping endpoints.
Parameter and group attributes are available in Scramble 0.12.x
.
Introducing operation transformers API
The next discovery came when I tried to dynamically document Cachet API authentication. All of Cachet’s GET
endpoints are public by design, so it made sense to automatically document this, rather than requiring maintainers to manually add @unauthenticated
PHPDoc tags to each GET
method.
Scramble already had an OperationExtension
extension type, which allows modifying (transforming 🧠) any operation’s documentation. In OpenAPI terms, an “operation” represents a single route.
But then I realized — creating an entire class just for 3 lines of logic is terrible DX! It would be much better if you could just pass a closure to Scramble’s config and be done with it.
This sent me down a rabbit hole of rethinking the whole configuration approach! Plus, there were requests on GitHub to allow explicitly controlling the order in which extensions run — so I decided to tackle that too.
First, I renamed “operation extension” to “operation transformer” because it better describes the intent.
Then I looked for inspiration in Laravel itself. Laravel’s middleware configuration felt like a great fit:
use App\Http\Middleware\EnsureTokenIsValid;
->withMiddleware(function (Middleware $middleware) { $middleware->append(EnsureTokenIsValid::class);})
This is perfect for the transformers API. It lets you control order, pass closures directly, or provide multiple transformers at once.
use Dedoc\Scramble\Configuration\OperationTransformer;
public function boot(){ Scramble::configure() ->withOperationTransformers(function (Operation $operation) { $operation->addParameters([ new Parameter('X-Rate-Limited', 'header'), ]); });}
This felt great and simplified Scramble’s configuration for Cachet dramatically (that PR is still WIP).
Scramble also had a way to modify (transform 🧠) the whole OpenAPI document after generation (you can add auth requirement there, etc). But previously, you could only have one such modifier per configuration. So I introduced “document transformers” to allow defining multiple transformers (with ordering!) for the final document too.
use Dedoc\Scramble\Configuration\DocumentTransformers;
public function boot(){ Scramble::configure() ->withDocumentTransformers(function (DocumentTransformers $transformers) { $transformers ->prepend(AddAuthInfo::class) ->append(AddOperationAuthInfo::class); });}
Transformers API is available in Scramble 0.12.x
.
Automating specification generation on CI
This part was both challenging and rewarding!
Cachet documentation lives in a separate repo: https://github.com/cachethq/docs. Meanwhile, the API source code lives here: https://github.com/cachethq/core.
The OpenAPI document is part of the documentation repo, but its generation happens in the cachethq/core
repo.
The ideal DX here: every time core
changes, the OpenAPI document in the docs repo should update automatically.
Here’s what I implemented:
- In GitHub Actions,
dedoc/scramble-pro
gets installed (to provide support for the packages Cachet uses). - After that, the OpenAPI spec gets generated using
scramble:export
. - Then, the action clones the docs repo into a subfolder.
- The spec file is placed in the appropriate folder inside the docs repo (
docs-repository/api-reference/openapi.json
). - Finally, the action commits and pushes changes (if any) to the docs repo.
This could still be improved! Ideally, the action would push to a dedicated branch and open a PR if one doesn’t already exist. That way, maintainers could manually review and accept changes.
You can check the PR with this action here: https://github.com/cachethq/core/pull/178
Conclusion
Integrating Scramble with Cachet was an eye-opening experience. It gave me tons of ideas for Scramble APIs — many of which I implemented and shared in this post.
You can check out the resulting Cachet API reference here: https://docs.cachethq.io/api-reference/introduction
If you need help integrating Scramble with your API, feel free to reach out: roman@dedoc.co!
Let me know if you have any questions!