How PostHog Uses drf-spectacular with Django REST Framework for Automatic OpenAPI Schema Generation
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, where drf_spectacular is added to INSTALLED_APPS and a comprehensive SPECTACULAR_SETTINGS dictionary controls the generation behavior:
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:
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), 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:
@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 and 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:
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 to properly document its Personal API Key authentication scheme:
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:
"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, 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.pydefines authentication schemes, hooks, and enum overrides throughSPECTACULAR_SETTINGS. - PostHogAutoSchema extends drf-spectacular's
AutoSchemato handle dynamicteam_idandproject_idparameters injected byTeamAndOrgViewSetMixin. - @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 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. 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →