Skip to content

API Reference

The api-reference package renders an OpenAPI 3.x document as a browsable API reference — tag-grouped navigation, per-operation parameter and schema trees, generated cURL and JavaScript snippets, a copy-as-Markdown button, and a request playground that executes real requests from the browser. It pairs naturally with Spectacular, which generates OpenAPI documents from a Laravel application, but renders any valid document.

The component below is live — browse the operations, inspect schemas, and switch snippet languages.

Terminal window
composer require lattice-php/api-reference

The package ships its React renderer as source, and the lattice() Vite plugin compiles it into your app’s bundle via virtual:lattice/plugins (see Component packages). The PHP component is picked up by Lattice’s discovery automatically.

The component styles itself with Tailwind utilities, so add the package’s stylesheet to your Tailwind entry after the Lattice import — its @source directive makes your build scan the package’s components:

@import "@lattice-php/lattice/css";
@import "@lattice-php/api-reference/css";

Pass a decoded OpenAPI document to the component on any page. With Spectacular installed, Scramble’s generator produces the document — cache it rather than regenerating per request:

use Dedoc\Scramble\Generator;
use Illuminate\Support\Facades\Cache;
use Lattice\ApiReference\ApiReference;
use Lattice\Core\Attributes\AsPage;
use Lattice\Http\Page;
use Lattice\Ui\PageSchema;
#[AsPage(route: 'docs', name: 'docs', middleware: ['auth'])]
final class ApiDocsPage extends Page
{
public function render(PageSchema $schema, Generator $generator): PageSchema
{
$document = Cache::rememberForever(
'openapi.document',
fn (): array => $generator(),
);
return $schema->schema([ApiReference::make()->spec($document)]);
}
}

Alternatively ->url('/openapi.json') makes the browser fetch the document from a URL instead of embedding it in the page props.

ApiReference::make()
->spec($document)
->tag(['Users', 'Roles']) // only these navigation groups
->defaultOperation('users.index') // initial selection
->title('Acme API') // overrides info.title
->hideHeader() // drop the title/version header
->hideBaseUrl() // drop the server picker
->expandDepth(3) // schema tree levels expanded by default
->twoColumnBreakpoint(Breakpoint::Xl);

->operation('users.show') pins the reference to a single operation with no navigation — useful for embedding one endpoint’s documentation inside another page.

The selected operation is mirrored to the URL hash, so operation links deep-link and survive reloads.

Alongside description, which renders as prose, the reference reads an x-tooltip extension — a short piece of HTML, typically a link to deeper documentation, revealed from an info button next to the field. Write it as a sibling of description on a schema property, a parameter (at parameter level, not inside its schema), or an operation:

{
"summary": "List users",
"description": "Returns every user in the account.",
"x-tooltip": "<a href=\"https://docs.example.com/users\">User guide</a>",
"parameters": [
{
"name": "filter[type]",
"in": "query",
"description": "Restricts the result set by type.",
"x-tooltip": "<a href=\"https://docs.example.com/filtering\">Filtering guide</a>"
}
]
}

Every operation carries a playground: path, query, and header parameters render as typed inputs (enum parameters become selects), JSON request bodies get an editor pre-filled with an example derived from the schema, and Execute sends the request from the browser against the server selected in the picker — responses stream into a live panel with status and headers.

ApiReference::make()
->spec($document)
->token($apiToken);

->token() pre-fills Authorization: Bearer for operations whose security scheme accepts a bearer token. Requests are plain browser fetch calls, so the API must be same-origin or send CORS headers for the reference’s origin.

A static token exists for the whole page life and carries one fixed scope set. With a remote source the playground instead fetches a short-lived access token scoped to exactly the operation being executed, the first time Execute is pressed:

ApiReference::make('api-docs')
->id('api-docs')
->spec($document)
->tokenSource('api-docs-tokens', audience: $tenant->slug);

->tokenSource() reads every operation’s bearer scope set out of the inline spec and seals a signed remote token access per distinct set — the browser can only request exactly those scope combinations, anything else fails the ref check. On execute, the playground resolves a token through the source’s issueBrowserToken() (cached per scope set until shortly before expiry, retried once on a 401), so no credential is minted just by opening the page. audience is sealed alongside the scopes and is the natural place for a tenant identifier. When both are configured, tokenSource() wins over token().

->tokenSource() is the Laravel side of this mechanism; hosts without a Lattice backend get the same lazy-on-execute behaviour through the resolveAccessToken prop described under Use without Laravel.

The renderer is published to npm as @lattice-php/api-reference, so any React frontend — an Astro docs site, a Vite SPA — renders the same reference without a Lattice backend:

Terminal window
npm install @lattice-php/api-reference

Import the stylesheet into your Tailwind v4 entry. It pulls in the Lattice design tokens and adds the @source directives that make your build scan the package’s compiled components:

@import "tailwindcss";
@import "@lattice-php/api-reference/css";

Render the component with a decoded OpenAPI document, or a url the browser fetches:

import { ApiReference } from "@lattice-php/api-reference";
import spec from "./openapi.json";
export default function ApiDocs() {
return <ApiReference spec={spec} />;
}

For lazy per-operation tokens without any backend, pass resolveAccessToken — the playground calls it on Execute with the operation’s bearer scope set and puts the result into the Authorization header:

<ApiReference
spec={spec}
resolveAccessToken={async ({ scopes, forceRefresh }) => {
const response = await fetch("https://auth.example.com/playground-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ scopes }),
});
const { access_token, expires_in } = await response.json();
return { accessToken: access_token, expiresIn: expires_in };
}}
/>

Return expiresIn (seconds) to let the playground cache the token per scope set until shortly before expiry; a plain string is never cached, so the host owns reuse. On a 401 the playground calls the resolver once more with forceRefresh: true and retries the request. A rejected promise’s message is shown next to Execute. resolveAccessToken wins over remoteTokens and token.

Props mirror the PHP component — operation, tags, defaultOperation, title, hideHeader, hideBaseUrl, expandDepth, twoColumnBreakpoint, token, resolveAccessToken — plus two selection controls:

  • deepLinking (default true) mirrors the expanded operation to location.hash. Pass false when the host page owns the hash.
  • selectedOperation / onOperationChange switch the reference to controlled selection for hosts with their own routing.

Icons ship inside the package: the component provides its own sprite when no SpriteProvider is mounted above it, and a host-provided sprite always wins.

Mount the component as a React island. It renders on the server, so client:load prerenders the reference into the built HTML:

---
import ApiDocs from "../components/ApiDocs";
---
<ApiDocs client:load />

The breakdown below measures the package’s built plugin — React and the framework runtime are external, so this is exactly what the API reference adds on top of an app that already ships Lattice. It is regenerated on every docs build.

24.0 KB gzipped · 94.2 KB raw JavaScript

DependencyRawGzipShare
@lattice-php/api-reference83.2 KB22.9 KB95.5%
Bundler runtime11.0 KB1.1 KB4.5%

Emitted JavaScript files

FileRawGzip
plugin.js94.2 KB24.0 KB

Open the full interactive treemap ↗ · Generated by Sonda 0.14.0 on Fri, 11 Sep 2026 17:20:08 GMT.