API Documentation — Scalar & OpenAPI 3.1
Why interactive API documentation?
Section titled “Why interactive API documentation?”An API without discoverable documentation creates friction: frontend developers read source code, QA teams guess request shapes, and onboarding new team members takes days instead of minutes. Static Swagger pages help, but they lack real authentication flows — testers still reach for Postman or curl.
Granit.Http.ApiDocumentation generates versioned OpenAPI 3.1 documents and serves Scalar, a modern interactive UI where developers can authenticate with OAuth2/PKCE and execute requests directly in the browser. One document is generated per API major version, so consumers always find the spec that matches their integration. The result: self-service API exploration from day one, fewer support requests, and documentation that stays in sync with the code automatically.
[DependsOn(typeof(GranitHttpApiDocumentationModule))]public class AppModule : GranitModule { }{ "ApiDocumentation": { "Title": "Clinic API", "MajorVersions": [1, 2], "Description": "Patient and appointment management API" }}{ "ApiDocumentation": { "Title": "Clinic API", "MajorVersions": [1], "OAuth2": { "AuthorizationUrl": "https://keycloak.example.com/realms/clinic/protocol/openid-connect/auth", "TokenUrl": "https://keycloak.example.com/realms/clinic/protocol/openid-connect/token", "ClientId": "clinic-scalar", "EnablePkce": true, "Scopes": ["openid", "profile"] } }}When OAuth2 is fully configured, the Bearer scheme is replaced with an OAuth2
Authorization Code flow in the OpenAPI document, and Scalar enables interactive
PKCE-based authentication.
In Program.cs:
app.UseGranitApiDocumentation(); // Maps /openapi/v1.json, /openapi/v2.json, /scalarSchema examples
Section titled “Schema examples”Provide realistic example values for request DTOs without coupling to OpenAPI:
public class AppointmentSchemaExamples : ISchemaExampleProvider{ public IReadOnlyDictionary<Type, JsonNode> GetExamples() => new Dictionary<Type, JsonNode> { [typeof(CreateAppointmentRequest)] = new JsonObject { ["patientId"] = "d4e5f6a7-1234-5678-9abc-def012345678", ["doctorId"] = "a1b2c3d4-5678-9abc-def0-123456789abc", ["scheduledAt"] = "2026-04-15T09:30:00Z", ["durationMinutes"] = 30 } };}Implementations of ISchemaExampleProvider are auto-discovered at startup.
Internal API exclusion
Section titled “Internal API exclusion”Exclude inter-service endpoints from public documentation:
app.MapPost("/webhooks/keycloak", HandleKeycloakWebhook) .WithMetadata(new InternalApiAttribute());Document transformers
Section titled “Document transformers”The module registers these OpenAPI transformers automatically:
| Transformer | Purpose |
|---|---|
JwtBearerSecuritySchemeTransformer | Adds Bearer security scheme when JWT is configured |
OAuth2SecuritySchemeTransformer | Replaces Bearer with OAuth2 Authorization Code when configured |
SecurityRequirementOperationTransformer | Anonymous endpoints override global security |
ProblemDetailsSchemaDocumentTransformer | Adds RFC 7807 ProblemDetails schema |
ProblemDetailsResponseOperationTransformer | Documents 4xx/5xx Problem Details responses |
TenantHeaderOperationTransformer | Documents X-Tenant-Id header when enabled |
InternalApiDocumentTransformer | Removes [InternalApi] endpoints |
WolverineOpenApiOperationTransformer | Enhances Wolverine HTTP endpoint documentation |
SchemaExampleSchemaTransformer | Applies ISchemaExampleProvider examples |
Configuration reference
Section titled “Configuration reference”| Property | Default | Description |
|---|---|---|
Title | "API" | OpenAPI document and Scalar UI title |
MajorVersions | [1] | Major version numbers to document |
Description | null | OpenAPI description (Markdown supported) |
PartyEmail | null | Party email in OpenAPI info |
LogoUrl | null | Logo URL for Scalar sidebar |
FaviconUrl | null | Favicon for Scalar page |
EnableInProduction | false | Expose docs in Production |
EnableTenantHeader | false | Document required tenant header |
TenantHeaderName | "X-Tenant-Id" | Tenant header name |
AuthorizationPolicy | null | Policy for doc endpoints (null = inherit, "" = anonymous) |
OAuth2.AuthorizationUrl | null | OAuth2 authorization endpoint |
OAuth2.TokenUrl | null | OAuth2 token endpoint |
OAuth2.ClientId | null | Public OAuth2 client ID (PKCE-capable) |
OAuth2.EnablePkce | true | Enable PKCE with S256 |
OAuth2.Scopes | ["openid"] | OAuth2 scopes to request |
Public API summary
Section titled “Public API summary”| Category | Key types | Package |
|---|---|---|
| Module | GranitHttpApiDocumentationModule | — |
| Options | ApiDocumentationOptions, OAuth2Options | Granit.Http.ApiDocumentation |
| Extension points | ISchemaExampleProvider, InternalApiAttribute | Granit.Http.ApiDocumentation |
| Extensions | AddGranitApiDocumentation(), UseGranitApiDocumentation() | Granit.Http.ApiDocumentation |
See also
Section titled “See also”- ADR-009: Scalar — Why Scalar was chosen over Swagger UI
- API Versioning — URL versioning, RFC 8594 deprecation
- Authentication module — JWT Bearer, Keycloak
- API & Http overview — All HTTP infrastructure packages