# How PostHog Uses drf-spectacular with Django REST Framework for Automatic OpenAPI Schema Generation

> Learn how PostHog leverages drf-spectacular with Django REST Framework to automatically generate OpenAPI schemas. Discover custom schema implementation and endpoint enrichment techniques.

- Repository: [PostHog/posthog](https://github.com/PostHog/posthog)
- Tags: how-to-guide
- Published: 2026-04-25

---

**PostHog generates its OpenAPI 3.0 specification by configuring drf-spectacular in Django settings, implementing a custom `PostHogAutoSchema` class to handle dynamic path parameters, and decorating viewsets with `@extend_schema` to enrich endpoint metadata.**

PostHog's API is built on Django REST Framework (DRF) and leverages **drf-spectacular** to automatically produce a complete OpenAPI specification. This integration eliminates manual documentation maintenance while ensuring the generated schema accurately reflects the actual API surface, which is then consumed by the frontend TypeScript SDK generation pipeline and external tools.

## Configuring drf-spectacular via SPECTACULAR_SETTINGS

The integration begins in [`posthog/settings/web.py`](https://github.com/PostHog/posthog/blob/main/posthog/settings/web.py), where `drf_spectacular` is added to `INSTALLED_APPS` and a comprehensive `SPECTACULAR_SETTINGS` dictionary controls the generation behavior:

```python
SPECTACULAR_SETTINGS = {
    "AUTHENTICATION_WHITELIST": ["posthog.auth.PersonalAPIKeyAuthentication"],
    "GET_MOCK_REQUEST": "posthog.api.documentation.build_openapi_mock_request",
    "PREPROCESSING_HOOKS": ["posthog.api.documentation.preprocess_exclude_path_format"],
    "POSTPROCESSING_HOOKS": [
        "drf_spectacular.hooks.postprocess_schema_enums",
        "posthog.api.documentation.custom_postprocessing_hook",
    ],
    "ENUM_NAME_OVERRIDES": {
        # Maps enum names to model paths or literal lists for stable naming

    },
}

```

These settings configure several critical aspects of the schema generation. The **authentication whitelist** restricts visible auth classes to `PersonalAPIKeyAuthentication`, while `GET_MOCK_REQUEST` provides a fake request instance during schema generation. **Pre-processing hooks** filter out internal paths, and **post-processing hooks** normalize enum names across the specification. drf-spectacular reads these settings automatically at import time, applying them to all subsequent schema generation operations.

## Handling Dynamic Path Parameters with PostHogAutoSchema

Many PostHog endpoints use `TeamAndOrgViewSetMixin` to inject dynamic parameters like `team_id` and `project_id` at runtime. Since DRF cannot infer these router-mixin parameters from model relationships, the default schema generation produces warnings. PostHog solves this by subclassing drf-spectacular's `AutoSchema` in [`posthog/api/documentation.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/documentation.py):

```python
class PostHogAutoSchema(AutoSchema):
    """Silences path-parameter warnings for params handled by TeamAndOrgViewSetMixin."""
    
    def _resolve_path_parameters(self, variables):
        parameters = []
        for variable in variables:
            if variable in _KNOWN_PATH_PARAMS:
                parameters.append(
                    build_parameter_type(
                        name=variable,
                        location=OpenApiParameter.PATH,
                        description=_KNOWN_PATH_PARAMS[variable]["description"],
                        schema=_KNOWN_PATH_PARAMS[variable]["schema"],
                    )
                )
            else:
                # Fallback to default behavior for other variables

                pass
        return parameters

```

The `_KNOWN_PATH_PARAMS` dictionary maps each custom parameter to its OpenAPI type (typically `str` or `int`) and description. This subclass is wired into DRF through the `"DEFAULT_SCHEMA_CLASS"` setting in `REST_FRAMEWORK` (defined in [`posthog/settings/web.py`](https://github.com/PostHog/posthog/blob/main/posthog/settings/web.py)), ensuring all viewsets use `PostHogAutoSchema` by default.

## Enriching Endpoints with @extend_schema Decorators

Individual viewsets provide fine-grained metadata using the `@extend_schema` decorator from drf-spectacular. This pattern appears throughout the codebase, particularly in product-specific API modules like [`products/web_analytics/backend/api.py`](https://github.com/PostHog/posthog/blob/main/products/web_analytics/backend/api.py):

```python
@extend_schema(
    operation_id="web_analytics_weekly_digest",
    summary="Summarize web analytics",
    description="Summarizes a project's web analytics for the specified lookback period.",
    parameters=[
        OpenApiParameter(
            name="days",
            type=OpenApiTypes.INT,
            location=OpenApiParameter.QUERY,
            default=7,
            description="Lookback window in days (1–90).",
        ),
        OpenApiParameter(
            name="compare",
            type=OpenApiTypes.BOOL,
            location=OpenApiParameter.QUERY,
            default=True,
            description="Include period-over-period change calculation.",
        ),
    ],
    responses={200: OpenApiResponse(response=WeeklyDigestResponseSerializer)},
    tags=["web_analytics"],
)
@action(detail=False, methods=["get"], url_path="weekly_digest")
def weekly_digest(self, request, **kwargs):
    # Implementation here

    pass

```

The decorator accepts `operation_id` for client generation, `summary` and `description` for documentation, `parameters` for query/path inputs, `responses` for response schemas, and `tags` for API organization. Similar implementations exist in [`products/dashboards/backend/api/dashboard.py`](https://github.com/PostHog/posthog/blob/main/products/dashboards/backend/api/dashboard.py) and [`products/llm_analytics/backend/api/score_definitions.py`](https://github.com/PostHog/posthog/blob/main/products/llm_analytics/backend/api/score_definitions.py).

## Exposing the Schema via URL Configuration

The generated OpenAPI specification is served through dedicated endpoints configured in [`posthog/urls.py`](https://github.com/PostHog/posthog/blob/main/posthog/urls.py):

```python
urlpatterns = [
    path("api/schema/", SpectacularAPIView.as_view(), name="schema"),
    path("api/schema/swagger-ui/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
    path("api/schema/redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
    # ... other API routes

]

```

**`SpectacularAPIView`** returns the raw OpenAPI JSON at `/api/schema/`, while **`SpectacularSwaggerView`** and **`SpectacularRedocView`** provide interactive documentation interfaces. These views consume the schema generated by the configured `PostHogAutoSchema` class and exposed decorators.

## Customizing Authentication Documentation

PostHog implements a custom authentication extension in [`posthog/api/documentation.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/documentation.py) to properly document its Personal API Key authentication scheme:

```python
class PersonalAPIKeyScheme(OpenApiAuthenticationExtension):
    target_class = "posthog.auth.PersonalAPIKeyAuthentication"
    name = "PersonalAPIKeyAuth"

    def get_security_requirement(self, auto_schema):
        # Dynamically extracts required scopes from APIScopePermission

        pass

    def get_security_definition(self, auto_schema):
        return {"type": "http", "scheme": "bearer"}

```

This extension, registered via the `"AUTHENTICATION_WHITELIST"` in `SPECTACULAR_SETTINGS`, ensures the generated OpenAPI document includes the correct security scheme:

```json
"securitySchemes": {
  "PersonalAPIKeyAuth": {
    "type": "http",
    "scheme": "bearer"
  }
}

```

## Post-processing Hooks and Enum Handling

The `POSTPROCESSING_HOOKS` list in `SPECTACULAR_SETTINGS` includes `custom_postprocessing_hook` from [`posthog/api/documentation.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/documentation.py), which normalizes enum names to ensure stability across releases. Combined with the `ENUM_NAME_OVERRIDES` mapping, this prevents generated TypeScript enums from changing names when underlying model fields are refactored. The preprocessing hook `preprocess_exclude_path_format` simultaneously filters out internal or experimental endpoints from the public schema.

## Summary

- **Global configuration** in [`posthog/settings/web.py`](https://github.com/PostHog/posthog/blob/main/posthog/settings/web.py) defines authentication schemes, hooks, and enum overrides through `SPECTACULAR_SETTINGS`.
- **PostHogAutoSchema** extends drf-spectacular's `AutoSchema` to handle dynamic `team_id` and `project_id` parameters injected by `TeamAndOrgViewSetMixin`.
- **@extend_schema decorators** on viewsets provide operation IDs, parameters, response serializers, and tags for accurate endpoint documentation.
- **URL endpoints** expose the JSON schema at `/api/schema/` and interactive UIs via Swagger and Redoc views.
- **PersonalAPIKeyScheme** customizes the security documentation to reflect PostHog's bearer token authentication.

## Frequently Asked Questions

### What is drf-spectacular and why does PostHog use it for OpenAPI generation?

drf-spectacular is a third-party library that automatically generates OpenAPI 3.0 schemas from Django REST Framework code. PostHog uses it to eliminate the maintenance burden of hand-written OpenAPI specifications while ensuring the API documentation remains synchronized with the actual implementation as the codebase evolves.

### How does PostHog handle dynamic path parameters like team_id in the OpenAPI schema?

PostHog implements a custom `PostHogAutoSchema` class in [`posthog/api/documentation.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/documentation.py) that overrides the `_resolve_path_parameters` method. This class maintains a `_KNOWN_PATH_PARAMS` dictionary mapping dynamic parameters (such as `team_id` and `project_id`) to their OpenAPI types and descriptions, silencing warnings that would otherwise occur because these parameters are injected at runtime by `TeamAndOrgViewSetMixin` rather than inferred from model relationships.

### Where are the OpenAPI schema endpoints exposed in PostHog?

The schema endpoints are defined in [`posthog/urls.py`](https://github.com/PostHog/posthog/blob/main/posthog/urls.py). The raw JSON specification is available at `/api/schema/` via `SpectacularAPIView`, while interactive documentation interfaces are provided at `/api/schema/swagger-ui/` (Swagger UI) and `/api/schema/redoc/` (ReDoc), both consuming the same generated schema.

### How does PostHog document its Personal API Key authentication in the OpenAPI specification?

PostHog defines a `PersonalAPIKeyScheme` class that extends `OpenApiAuthenticationExtension` in [`posthog/api/documentation.py`](https://github.com/PostHog/posthog/blob/main/posthog/api/documentation.py). This extension specifies the bearer token authentication scheme and is registered in `SPECTACULAR_SETTINGS` under `AUTHENTICATION_WHITELIST`, causing drf-spectacular to include the appropriate `http/bearer` security definition in the generated OpenAPI document.