masitings / geni-api
Static-analysis OpenAPI documentation generator for Laravel
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.2
- illuminate/auth: ^10.0|^11.0|^12.0|^13.0
- illuminate/cache: ^10.0|^11.0|^12.0|^13.0
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/contracts: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
- illuminate/view: ^10.0|^11.0|^12.0|^13.0
- nikic/php-parser: ^5.0
- phpstan/phpdoc-parser: ^2.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.13
- lorisleiva/laravel-actions: ^2.0|^3.0
- opis/json-schema: ^2.3
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.34|^3.0|^4.0|^5.0
- pestphp/pest-plugin-arch: ^2.7|^3.0|^4.0|^5.0
- pestphp/pest-plugin-laravel: ^2.4|^3.0|^4.0|^5.0
- spatie/laravel-data: ^4.0
- spatie/laravel-query-builder: ^5.0|^6.0|^7.0
Suggests
- lorisleiva/laravel-actions: Enables automatic OpenAPI documentation for routes defined as Laravel Actions (^2.0|^3.0).
- spatie/laravel-data: Required to automatically infer OpenAPI schemas from Data DTOs (^4.0).
- spatie/laravel-query-builder: Enables automatic OpenAPI query parameter documentation from Spatie QueryBuilder calls (^5.0|^6.0|^7.0).
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-18 12:46:34 UTC
README
Geni is a static-analysis OpenAPI 3.1.0 documentation generator for Laravel. It reads your source files (routes, controllers, Form Requests, Resources, migrations) and produces a specification without ever booting your application or connecting to a database.
It includes a built-in standalone interactive documentation portal (BladeRenderer) that runs out-of-the-box with zero JavaScript build step (no React, no Vite, no node_modules required).
Geni supports standard OpenAPI docblock conventions and PHP 8 attributes, so documentation already written as PHPDoc tags and attributes carries over without changes.
📖 Full Documentation: See geni.masiting.dev for complete architecture details, configuration reference, PHP attributes, and end-to-end examples.
Key Highlights
- Zero database connections. Column types are reconstructed offline from migration files.
- Zero application boot for inference. Route discovery is the only runtime touchpoint; everything else is static AST analysis.
- Responses as Laravel sends them.
JsonResourcekeys are typed from migrations and$casts, relations, nested resources and conditional helpers, wrapped indata(or{data, links, meta}when paginated), with201for newly created models. - Customizable. Transform the document or individual operations with
Geni::configure(), add rule and exception mappings, configureservers. - Standalone docs UI out of the box. Powered by Blade, Tailwind Play CDN, and Alpine.js with live search, Prism.js syntax highlighting, Markdown descriptions and an interactive Try It console. Prefer a hosted UI? Switch to Scalar or Stoplight Elements with one config value.
- Access control. Docs are open in
local; elsewhere aviewApiDocsgate decides, or protect them with isolated form-based / HTTP Basic credentials independent of application user tables. - Runs anywhere your code does (local checkouts, CI pipelines, pre-commit hooks).
Installation
composer require masitings/geni-api
Publish the configuration file:
php artisan vendor:publish --tag=geni-config
Quick Start
-
Configure migration paths and API routes in
config/geni.php(defaults todatabase_path('migrations')and/api/*). -
Export a specification file:
php artisan geni:export --path=openapi.json
-
Or browse the interactive documentation live in your browser at
/docs/api(open in thelocalenvironment; see Access & UI for other environments).
No annotations required: for endpoints using route-model binding, validated request bodies, and JsonResource responses, Geni infers schemas automatically. Operation titles and descriptions come from the controller method's PHPDoc: the first paragraph is the summary, the rest the description (Markdown supported).
Upgrading from 2.x? See the Upgrading from 2.x steps in CHANGELOG.md: MCP
tools/callover HTTP is now opt-in and committed specs must be regenerated.
Commands Overview
| Command | Description |
|---|---|
php artisan geni:export {--path=} {--api=} |
Export the OpenAPI specification to a file |
php artisan geni:mcp {--path=} {--format=} {--base-url=} |
Generate an MCP tool manifest or client configuration for AI agents |
php artisan geni:mcp:serve {--api=} {--base-url=} {--timeout=} |
Run live MCP stdio server responding to tools/list and executing tools/call |
php artisan geni:check {--path=} {--api=} |
Compare current routes against committed spec; exits non-zero on drift |
php artisan geni:analyze {--json} {--api=} |
Inspect all unresolved inference diagnostics |
php artisan geni:cache {--api=} / geni:clear {--api=} |
Pre-generate and cache the document, or clear cache |
--api must be default or a key of geni.apis; any other name fails the command.
Model Context Protocol (MCP) Integration
Geni natively exports and executes your Laravel API as Model Context Protocol (MCP) tools, turning your application into an AI-ready toolkit. AI agents (Claude Code, Cursor, Windsurf, Claude Desktop) can discover, understand, and execute your endpoints directly without manual wrapper code or extra setup.
What AI Agents Can Do With Your API
- Autonomous Endpoint Discovery (
tools/list): AI assistants automatically read all your routes, complete with request body parameters, validation constraints (required, email, min, max, enum), and response models inferred from your source code. - Direct Action Execution (
tools/call): An AI agent can perform real tasks via your API. For example, ask your agent: "Find the user with email alex@example.com and set their role to editor", and the agent will callGET /api/usersandPATCH /api/users/{id}autonomously! - Safe Credential Injection: Configure your Bearer token or API key in your environment. Credentials are automatically attached during execution and never exposed to the LLM context or tool schemas.
1. Live Stdio Execution (php artisan geni:mcp:serve)
Configure Claude Desktop or Claude Code to run Geni's built-in MCP server:
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"my-laravel-api": {
"command": "php",
"args": ["artisan", "geni:mcp:serve"],
"cwd": "/path/to/laravel-app",
"env": {
"GENI_MCP_BEARER_TOKEN": "your-api-bearer-token",
"GENI_MCP_API_KEY": "your-api-key"
}
}
}
}
Claude Code (.mcp.json):
{
"mcpServers": {
"api": {
"command": "php",
"args": ["artisan", "geni:mcp:serve"]
}
}
}
2. Declarative Tools Manifest & Remote HTTP Discovery
- Export Manifest File:
php artisan geni:mcp --path=mcp-manifest.json
- Generate Client Bridge Configuration:
php artisan geni:mcp --format=client-config --base-url=https://api.example.com
- Live HTTP Discovery & Execution Endpoint:
GET /docs/api/mcpreturns the standard MCPtools/listJSON envelope.POST /docs/api/mcpaccepts JSON-RPC 2.0 requests sent asapplication/json(initialize,tools/list,tools/call).tools/callover HTTP is off by default because it runs endpoints with the server's configured credentials for anyone who can open the docs. Enable it withGENI_MCP_HTTP_EXECUTION=true(geni.mcp.http_execution).geni:mcp:serveis not affected.
Access & UI
Outside the local environment the docs return 404 unless you define a viewApiDocs gate:
// AppServiceProvider::boot() Gate::define('viewApiDocs', fn (?User $user) => in_array($user?->email, ['admin@example.com'], true));
User-based gates need a session: add 'web' to geni.middleware. A denying gate returns 403. Set restrict_to_local to false to rely only on docs_auth.
Pick the UI with geni.renderer (or renderer inside an entry of geni.apis):
| Renderer | UI |
|---|---|
blade (default) |
Built-in portal: version switcher, Try It console, MCP-friendly, no build step |
scalar |
Scalar API Reference; options in renderers.scalar.configuration are passed to Scalar as-is |
elements |
Stoplight Elements |
Scalar and Elements load pinned CDN builds; override script_url / stylesheet_url to self-host them. Descriptions written in PHPDoc support a safe Markdown subset in the built-in portal (paragraphs, lists, headings, fenced code, code, bold, italic, links).
Customizing the Document
Register transformers in a service provider's boot() method. Registrations for default also apply to every API in geni.apis; pass a name to target one API.
use Geni\Inference\Document\OpenApiDocument; use Geni\Inference\Document\Operation; use Geni\Laravel\DiscoveredRoute; use Geni\Laravel\Geni; Geni::configure() ->withDocumentTransformers(function (OpenApiDocument $document, string $apiName) { $document->info->description = 'Public API for Acme.'; }) ->withOperationTransformers(function (Operation $operation, DiscoveredRoute $route) { if (in_array('throttle:api', $route->middleware, true)) { $operation->responses['429'] = ['description' => 'Too many requests']; } }) ->withRuleTransformers(Ipv4CidrRule::class) // Geni\Inference\RuleTransformer ->withExceptionResponses(PaymentRequiredMapper::class); // Geni\Inference\ExceptionToResponse Geni::configure('v2')->afterOpenApiGenerated(fn (OpenApiDocument $document) => $document->info->title .= ' (beta)');
Classes implementing Geni\Laravel\Contracts\DocumentTransformer, Geni\Laravel\Contracts\OperationTransformer, Geni\Inference\RuleTransformer or Geni\Inference\ExceptionToResponse can also be listed in config('geni.extensions') (or geni.apis.{name}.extensions).
servers are configured as description => URL:
'servers' => ['Production' => 'https://api.example.com', 'Local' => '/'],
Operation IDs
Every operation gets an operationId, chosen in this order: #[Endpoint(operationId: ...)] or @operationId, then the route name without a leading api. (api.users.index → users.index), then the controller and method (UserController@show → user.show), made unique across the document. A PATCH documented next to PUT on the same route gets a _patch suffix. Generated ids do not rename MCP tools; set geni.mcp.tool_naming to operation_id to use them.
Resources & Responses
Given a resource like this:
/** @mixin \App\Models\User */ class UserResource extends JsonResource { public function toArray(Request $request): array { return [ 'id' => $this->id, // integer (migration) 'email_verified_at' => $this->email_verified_at, // ["string", "null"], date-time 'is_admin' => $this->is_admin, // boolean ($casts) 'status' => $this->status, // $ref UserStatus (enum cast) 'posts' => PostResource::collection($this->whenLoaded('posts')), // optional array of PostResource 'team' => new TeamResource($this->whenLoaded('team')), // optional; nullable if team_id is nullable 'posts_count' => $this->whenCounted('posts'), // optional integer ]; } }
Geni documents UserResource as a component with those types and a required list (conditional keys are optional), and the response the way Laravel sends it:
| Controller returns | Documented response |
|---|---|
new UserResource($user) |
{data: UserResource} (plus with() / additional() keys; $wrap respected) |
UserResource::collection(User::all()) |
{data: UserResource[]} |
UserResource::collection(User::paginate()) |
{data, links, meta} |
new UserResource(User::create($data)) |
201 |
$user (typed parameter), User::findOrFail($id) |
serialized User model ($hidden respected) |
response()->noContent() |
204 |
Multi-Version API Documentation
Document multiple versions (e.g. v1, v2) or separate domains independently with automatic version-switching in the UI:
// config/geni.php 'apis' => [ 'v1' => [ 'title' => 'API v1 (Legacy)', 'version' => '1.4.0', 'api_path' => 'api/v1', ], 'v2' => [ 'title' => 'API v2 (Latest)', 'version' => '2.0.0', 'api_path' => 'api/v2', ], ],
When multiple versions are defined:
- Dedicated routes are registered:
/docs/api/v1,/docs/api/v2,/docs/api/v1.json,/docs/api/v2.json,/docs/api/v1/mcp,/docs/api/v2/mcp. - An interactive version switcher dropdown automatically appears in the sidebar header of the documentation portal.
- Single-API setups (empty
apis => []) retain standard zero-overhead behavior without dropdowns.
Spatie Laravel Data Support
Geni provides first-class, opt-in support for Spatie Laravel Data (spatie/laravel-data):
- Request parameters: Controller action parameters type-hinting a class extending
Spatie\LaravelData\Dataautomatically generate the OpenAPI request body schema. - Responses: Controller return statements returning
new DataClass(...),DataClass::from(...),DataClass::collect(...), or declaring aDataClassreturn type hint generate accurate OpenAPI response schemas. - Validation attributes: Spatie validation attributes (
#[Required],#[Min],#[Max],#[Email],#[Url],#[Regex]) are statically mapped to JSON Schema constraints. - Nested Data & Collections: Nested Data classes and
DataCollectionreturns are recursively resolved and referenced. - Zero runtime overhead: Fully static AST analysis. The package never requires
spatie/laravel-dataat runtime.
Laravel Actions Support
Geni provides first-class, opt-in support for Laravel Actions (lorisleiva/laravel-actions):
- Single-action routes: Routes registered as
Route::get('/users', CreateUserAction::class)are automatically detected. Geni resolvesasController()(preferred) orhandle()as the execution method. - Rules extraction: When an action defines a
rules()method (or usesActionRequest), request validation rules are statically inferred into the OpenAPI request body schema. - Authorization: When an action defines an
authorize()method with non-trivial logic, a 403 Forbidden response is automatically documented. - Summary derivation: Operation summaries are cleanly derived from action class names (e.g.
CreateUserAction-> "Create user") when no explicit summary annotation is provided.
Spatie Laravel Query Builder Support
Geni statically inspects Spatie Laravel Query Builder (spatie/laravel-query-builder) calls on GET operations:
allowedFilters(...): Generatesfilter[field]query parameters with database column types automatically resolved from your migrations. SupportsAllowedFilter::exact,partial,scope, andtrashed.allowedSorts(...)anddefaultSort(...): Generates a unifiedsortquery parameter with expanded ascending and descending enums and default values.allowedIncludes(...): Generates anincludequery parameter listing available relation names.allowedFields(...): Generatesfields[resource]query parameters for sparse fieldsets.allowedAppends(...): Generates anappendquery parameter listing dynamic model accessors.
Design Decisions & Static Analysis Nuances
Geni parses code statically rather than evaluating runtime values:
Rule::in($variable): Non-literal variables cannot be expanded into an enum. Geni documents the base type and records a diagnostic. (Workaround: inline literal values, or add@var).- Column types from migrations: Relies on migration history. Pruned migrations via
schema:dumprequireschema_overrides. exists:table,column: Resolved offline from migration schemas rather than live database queries.
For in-depth guides on all features, custom renderers, security schemes, and PHP attributes, please read the full documentation at geni.masiting.dev.
License
The MIT License (MIT). Please see LICENSE for more information.

