Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.apivalk.com/llms.txt

Use this file to discover all available pages before exploring further.

For a ready-to-run script and CI wiring, see Generate OpenAPI and docblocks.

How it Works

The generator leverages the existing framework infrastructure to discover and process your API metadata:
  1. Route Discovery: It uses the Router to find all registered routes and their corresponding controller classes.
  2. Metadata Extraction: For each route, it resolves the associated request and response classes via AbstractApivalkController::getRequestClass() and ::getResponseClasses(), then calls getDocumentation() on those classes to obtain the declared ApivalkRequestDocumentation / ApivalkResponseDocumentation. Route-level metadata (filters, sortings, pagination, rate limit, authorization) is merged in from the Route object itself.
  3. Object Mapping: It maps the Apivalk Property system to OpenAPI objects (Schemas, Parameters, RequestBodies, Responses).
  4. Serialization: It assembles these objects into a final OpenAPI object and serializes it to JSON.

Core Logic

The OpenAPI Object

The OpenAPI class represents the root of the specification. It contains sub-objects for:
  • InfoObject: API title, version, description.
  • ServerObject: Base URLs for your API.
  • PathsObject: Map of endpoints and their methods.
  • ComponentsObject: Reusable schemas and security schemes.

The Generation Process

The OpenAPIGenerator coordinates several specialized generators:
  • PathsGenerator: Processes the list of routes.
  • PathItemGenerator: Handles a single URL (which may have multiple HTTP methods).
  • OperationGenerator: Handles a single HTTP method (GET, POST, etc.) on a path.
  • ParameterGenerator: Converts query and path properties to OpenAPI parameters.
  • RequestBodyGenerator: Converts body properties to a JSON request body schema.
  • ResponseGenerator: Converts response documentation to OpenAPI response schemas.

Usage Example

For a copy-paste bin/generate-openapi script and a complementary docblock-generation script, see the how-to: generate OpenAPI + docblocks.
use apivalk\apivalk\Documentation\OpenAPI\OpenAPIGenerator;
use apivalk\apivalk\Documentation\OpenAPI\Object\InfoObject;
use apivalk\apivalk\Documentation\OpenAPI\Object\ComponentsObject;
use apivalk\apivalk\Documentation\OpenAPI\Object\SecuritySchemeObject;
use apivalk\apivalk\Documentation\OpenAPI\Object\ServerObject;

// 1. Define API Metadata
$info = new InfoObject('My nice api', '1.0.0', 'Nice', 'Best API in ze world');

// 2. Define Components (Security Schemes)
// The "name" parameter of each SecuritySchemeObject is what you reference
// in RouteAuthorization on your routes. See "Security Schemes and Components" below.
$components = new ComponentsObject();
$components->setSecuritySchemes([
    SecuritySchemeObject::http(
        'api',                         // name — referenced by RouteAuthorization('api', ...)
        'bearer',                      // scheme
        'OAuth2 Bearer Authorization', // description
        'JWT'                          // bearerFormat
    )
]);

// 3. Instantiate the Generator
$generator = new OpenAPIGenerator(
    $apivalk, 
    $info, 
    [new ServerObject('http://localhost:8080', 'My local server')],
    $components
);

// 4. Generate the JSON string
$json = $generator->generate('json');

// 5. Output or save to file
header('Content-Type: application/json');
echo $json;

Security Schemes and Components

The ComponentsObject is where you define reusable elements for your OpenAPI specification, such as security schemes, common schemas, or parameters.

How Security Schemes Connect to Routes

The key concept: the name you give a SecuritySchemeObject is the same name you pass to RouteAuthorization on your routes. This is how Apivalk knows which security scheme protects which route, and how Swagger UI knows when to show the “Authorize” button.
SecuritySchemeObject::http('api', ...)       ← defines a scheme named "api"


RouteAuthorization('api', ['contract'], ...) ← this route requires the "api" scheme
When the OpenAPI spec is generated, Apivalk matches these names to produce the correct security entries per operation. Swagger UI then renders the “Authorize” button and applies the right credentials to the right endpoints.

Factory Methods

SecuritySchemeObject ships four static factories — one per scheme type. Each factory only exposes the parameters that are valid for that type, so you can’t accidentally pass bearerFormat to an apiKey scheme or in to an http scheme. The raw constructor still works but requires you to pass every field positionally, including nulls for fields that don’t apply. Use SecuritySchemeObject::TYPE_* constants wherever you need to reference a type string in your own code.

SecuritySchemeObject::http($name, $scheme, $description, $bearerFormat)

For HTTP authentication schemes (Basic, Bearer, Digest, …). The scheme value must be a registered IANA HTTP Authentication Scheme and is placed in the Authorization header by the client. bearerFormat is optional — it is a documentation hint only and has no effect on validation.
// JWT Bearer
SecuritySchemeObject::http('BearerAuth', 'bearer', 'JWT Bearer token', 'JWT');

// HTTP Basic
SecuritySchemeObject::http('BasicAuth', 'basic', 'HTTP Basic authentication');
Emits:
{
  "type": "http",
  "description": "JWT Bearer token",
  "scheme": "bearer",
  "bearerFormat": "JWT"
}
name and in are not emitted — they are invalid for http per the OpenAPI spec. The name is used only as the key in components.securitySchemes and to match RouteAuthorization.

SecuritySchemeObject::apiKey($name, $in, $description)

For raw API keys passed as a header, query parameter, or cookie. in must be one of "header", "query", or "cookie". The name is emitted in the spec as the actual header/parameter name the client must send (e.g. X-Api-Key).
// API key in a custom header
SecuritySchemeObject::apiKey('X-Api-Key', 'header', 'API key authentication');

// API key in a query string
SecuritySchemeObject::apiKey('api_key', 'query', 'API key as query parameter');
Emits:
{
  "type": "apiKey",
  "description": "API key authentication",
  "name": "X-Api-Key",
  "in": "header"
}
Note that for apiKey, name is emitted — it is the required field that tells the client which header or parameter to set.

SecuritySchemeObject::oauth2($name, $flows, $description)

For OAuth2. Requires an OAuthFlowsObject describing which grant types are supported. Each flow has its own URLs and scopes; pass null for flows you don’t support.
use apivalk\apivalk\Documentation\OpenAPI\Object\OAuthFlowsObject;
use apivalk\apivalk\Documentation\OpenAPI\Object\OAuthFlowObject;

SecuritySchemeObject::oauth2(
    'oauth2Auth',
    new OAuthFlowsObject(
        null,                         // implicit (deprecated, avoid)
        new OAuthFlowObject(          // authorization code
            'https://auth.example.com/authorize',
            'https://auth.example.com/token',
            'https://auth.example.com/token', // refresh
            ['read' => 'Read access', 'write' => 'Write access']
        ),
        null,                         // client credentials
        null                          // password
    ),
    'OAuth2 Authorization Code'
);
Emits:
{
  "type": "oauth2",
  "description": "OAuth2 Authorization Code",
  "flows": { ... }
}

SecuritySchemeObject::openIdConnect($name, $openIdConnectUrl, $description)

For OpenID Connect. Requires the well-known discovery URL of the provider. Swagger UI uses this URL to fetch scopes automatically.
SecuritySchemeObject::openIdConnect(
    'oidc',
    'https://auth.example.com/.well-known/openid-configuration',
    'OpenID Connect'
);
Emits:
{
  "type": "openIdConnect",
  "description": "OpenID Connect",
  "openIdConnectUrl": "https://auth.example.com/.well-known/openid-configuration"
}

End-to-End Example: JWT Bearer

Step 1: Define the security scheme in your ComponentsObject.
use apivalk\apivalk\Documentation\OpenAPI\Object\ComponentsObject;
use apivalk\apivalk\Documentation\OpenAPI\Object\SecuritySchemeObject;

$components = new ComponentsObject();
$components->setSecuritySchemes([
    SecuritySchemeObject::http(
        'api',                         // name — this is the link to your routes
        'bearer',                      // scheme
        'OAuth2 Bearer Authorization', // description
        'JWT'                          // bearerFormat
    )
]);
Step 2: Reference the same name in your route’s RouteAuthorization.
use apivalk\apivalk\Router\Route\Route;
use apivalk\apivalk\Security\RouteAuthorization;
use apivalk\apivalk\Router\RateLimit\IpRateLimit;
use apivalk\apivalk\Documentation\OpenAPI\Object\TagObject;

public static function getRoute(): Route
{
    return Route::post('/rest/v1/contract')
        ->description('Create a contract')
        ->rateLimit(new IpRateLimit('ip_rate_limit', 200, 10))
        ->tags([new TagObject('contract')])
        ->routeAuthorization(
            new RouteAuthorization('api', ['contract'], ['contract:create'])
            //                      ^^^
            //                      Must match the name from SecuritySchemeObject
        );
}
Step 3: Pass the components to the generator.
use apivalk\apivalk\Documentation\OpenAPI\OpenAPIGenerator;
use apivalk\apivalk\Documentation\OpenAPI\Object\InfoObject;
use apivalk\apivalk\Documentation\OpenAPI\Object\ServerObject;

$generator = new OpenAPIGenerator(
    $apivalk,
    new InfoObject('My nice api', '1.0.0', 'Nice', 'Best API in ze world'),
    [new ServerObject('http://localhost:8080', 'My local server')],
    $components // the ComponentsObject from step 1
);
The generated OpenAPI JSON will contain the security scheme under components.securitySchemes.api, and the /rest/v1/contract POST operation will have security: [{"api": ["contract"]}]. Swagger UI renders this as an “Authorize” button with a JWT bearer input.

End-to-End Example: Raw API Key Header

Not every API uses JWT. If your API authenticates via a raw API key sent in a custom header (e.g., X-API-Key), use the apiKey type: Step 1: Define the security scheme.
$components = new ComponentsObject();
$components->setSecuritySchemes([
    SecuritySchemeObject::apiKey(
        'apiKeyAuth',             // name — the link to your routes
        'header',                 // in — where the key is sent
        'API Key Authentication'  // description
    )
]);
Step 2: Reference it in your route.
public static function getRoute(): Route
{
    return Route::get('/rest/v1/reports')
        ->description('List reports')
        ->routeAuthorization(
            new RouteAuthorization('apiKeyAuth', ['reports'], ['reports:read'])
        );
}
Swagger UI will show an “Authorize” button that prompts for an API key value, and will send it as the X-API-Key header on matching requests.

End-to-End Example: OAuth2

For OAuth2, you can define full flows (authorization URL, token URL, scopes) so that Swagger UI can perform the OAuth2 flow directly: Step 1: Define the security scheme with OAuth2 flows.
use apivalk\apivalk\Documentation\OpenAPI\Object\OAuthFlowsObject;
use apivalk\apivalk\Documentation\OpenAPI\Object\OAuthFlowObject;

$components = new ComponentsObject();
$components->setSecuritySchemes([
    SecuritySchemeObject::oauth2(
        'oauth2Auth',              // name — the link to your routes
        new OAuthFlowsObject(
            null,                  // implicit flow
            new OAuthFlowObject(   // authorization code flow
                'https://example.com/api/oauth/authorize',
                'https://example.com/api/oauth/token',
                'https://example.com/api/oauth/refresh',
                [
                    'read' => 'Read access',
                    'write' => 'Write access'
                ]
            ),
            null,                  // client credentials flow
            null                   // password flow
        ),
        'OAuth2 Authentication'    // description
    )
]);
Step 2: Reference it in your route.
public static function getRoute(): Route
{
    return Route::get('/rest/v1/users')
        ->description('List users')
        ->routeAuthorization(
            new RouteAuthorization('oauth2Auth', ['user'], ['user:read'])
        );
}

Multiple Security Schemes

You can define multiple security schemes in a single ComponentsObject and use different ones on different routes:
$components = new ComponentsObject();
$components->setSecuritySchemes([
    SecuritySchemeObject::http('api', 'bearer', 'JWT Bearer', 'JWT'),
    SecuritySchemeObject::apiKey('apiKeyAuth', 'header', 'API Key'),
]);

// Route using JWT Bearer
Route::post('/rest/v1/contract')
    ->description('Create a contract')
    ->routeAuthorization(new RouteAuthorization('api', ['contract'], ['contract:create']));

// Route using API Key
Route::get('/rest/v1/status')
    ->description('Check system status')
    ->routeAuthorization(new RouteAuthorization('apiKeyAuth', ['status'], ['status:read']));

Automatic Security Scheme Generation (Fallback)

If you use security requirements in your routes but haven’t defined them in the ComponentsObject, the OpenAPIGenerator will attempt to automatically generate a base authorization part for you:
  • If the scheme name contains “bearer”, it defaults to an http type with a bearer scheme.
  • If the scheme name contains “oauth2”, it defaults to an oauth2 type with a basic password flow.
  • If the scheme name contains “fido”, it defaults to an apiKey type in the header.
  • Otherwise, it defaults to an apiKey type in the header.
This fallback exists as a convenience, but it is recommended to define your schemes explicitly so you have full control over descriptions, flows, and how Swagger UI renders the authorization interface.
Keep in mind: Future Auto-Discovery. Apivalk is moving towards full auto-discovery of security schemes. In the future, it will be able to automatically detect and document your security configuration (including names and versions) directly from your middleware and authenticators.

Automated Headers

Locale Headers (Accept-Language / Content-Language)

By default, the generator documents localization headers on every operation:
  • Request: An optional Accept-Language header parameter (BCP 47 language tag).
  • Response: A Content-Language header on every response, indicating the resolved locale.
This matches the runtime behavior of the MiddlewareStack, which always resolves the locale from the Accept-Language header and returns Content-Language. You can disable locale header documentation by passing false for the $documentLocaleHeaders parameter:
$generator = new OpenAPIGenerator(
    $apivalk,
    $info,
    [new ServerObject('http://localhost:8080', 'My local server')],
    $components,
    false // disable locale headers in the OpenAPI spec
);

Rate Limit Headers

When a route has a rate limit defined (via Route::rateLimit()), the generator automatically documents the following response headers on every response for that operation:
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the time window.
X-RateLimit-RemainingRequests remaining in the current window.
X-RateLimit-ResetUTC epoch timestamp when the window resets.
Retry-AfterUTC epoch timestamp after which the client may retry (present only when the limit is exceeded).
The header descriptions include the configured window duration from the RateLimitInterface. Routes without a rate limit will not have these headers in their documentation.

Automated Pagination Envelope

When a route has a pagination strategy attached via Route::pagination(...), the generator automatically wraps the matching list response’s schema in the standard pagination envelope (data, plus a pagination object whose shape matches the chosen strategy — page/page_size/total_pages for Pagination::page(), limit/offset/total for Pagination::offset(), limit/current_cursor/next_cursor for Pagination::cursor()). At runtime, attach the matching PaginationResponseInterface to your response via setPaginationResponse(...) — see pagination for the end-to-end flow.