> ## 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.

# OpenAPI Generator

> The `OpenAPIGenerator` is the component responsible for converting your code-based documentation into a standardized OpenAPI 3.0 (Swagger) specification.

<Tip>For a ready-to-run script and CI wiring, see [Generate OpenAPI and docblocks](/how-to/generate-openapi).</Tip>

## 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](/how-to/generate-openapi).

```php theme={null}
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](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml) 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.

```php theme={null}
// JWT Bearer
SecuritySchemeObject::http('BearerAuth', 'bearer', 'JWT Bearer token', 'JWT');

// HTTP Basic
SecuritySchemeObject::http('BasicAuth', 'basic', 'HTTP Basic authentication');
```

Emits:

```json theme={null}
{
  "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`).

```php theme={null}
// 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:

```json theme={null}
{
  "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.

```php theme={null}
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:

```json theme={null}
{
  "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.

```php theme={null}
SecuritySchemeObject::openIdConnect(
    'oidc',
    'https://auth.example.com/.well-known/openid-configuration',
    'OpenID Connect'
);
```

Emits:

```json theme={null}
{
  "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`.**

```php theme={null}
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`.**

```php theme={null}
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.**

```php theme={null}
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.**

```php theme={null}
$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.**

```php theme={null}
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.**

```php theme={null}
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.**

```php theme={null}
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:

```php theme={null}
$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:

```php theme={null}
$generator = new OpenAPIGenerator(
    $apivalk,
    $info,
    [new ServerObject('http://localhost:8080', 'My local server')],
    $components,
    false // disable locale headers in the OpenAPI spec
);
```

### Filter Documentation Style

By default, all filters for a route are grouped under a single `filter` parameter using OpenAPI's `style: deepObject` convention. Swagger UI renders each filter as a separate input labelled `filter[field]`, and clients send filters as `?filter[status]=active`.

```yaml theme={null}
- name: filter
  in: query
  required: false
  style: deepObject
  explode: true
  schema:
    type: object
    properties:
      status:
        type: string
        description: "Filter by status"
```

If you prefer each filter to appear as its own flat query parameter (`?status=active`), pass `true` for the `$flatFilters` parameter:

```php theme={null}
$generator = new OpenAPIGenerator(
    $apivalk,
    $info,
    [],
    null,
    true,  // documentLocaleHeaders
    true   // flatFilters — each filter becomes its own flat query param
);
```

With `flatFilters: false` (default):

* Swagger UI shows inputs labelled `filter[status]`, `filter[name]`, etc.
* Clients send `?filter[status]=active&filter[name]=Acme`

With `flatFilters: true`:

* Swagger UI shows inputs labelled `status`, `name`, etc.
* Clients send `?status=active&name=Acme`

**Both formats are accepted by the server at runtime regardless of this setting.** This option only controls how the filters are presented 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:

| Header                  | Description                                                                                     |
| ----------------------- | ----------------------------------------------------------------------------------------------- |
| `X-RateLimit-Limit`     | Maximum requests allowed in the time window.                                                    |
| `X-RateLimit-Remaining` | Requests remaining in the current window.                                                       |
| `X-RateLimit-Reset`     | UTC epoch timestamp when the window resets.                                                     |
| `Retry-After`           | UTC 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](/http/pagination) for the end-to-end flow.
