Documentación OpenAPI con drf-spectacular

Avanzado
Django
Django
Actualizado: 19/04/2026

Por qué documentar con OpenAPI

Una API REST sin documentación ES una API inutilizable. Los integradores (front-end, móvil, partners) necesitan saber:

  • Qué endpoints existen.
  • Qué parámetros acepta cada uno.
  • Qué forma tiene la respuesta.
  • Qué códigos de error puede devolver.
  • Cómo autenticarse.

Mantener la documentación a mano (en Confluence, Notion o un README) siempre se desactualiza. La única forma sostenible es generarla a partir del código mediante un schema OpenAPI (antes llamado Swagger).

DRF viene con un sistema de schema incorporado, pero limitado y deprecado. El estándar actual es drf-spectacular: genera OpenAPI 3.1 completo, con Swagger UI interactivo, y soporta todas las particularidades de DRF (polimorfismo, paginación, filtros, autenticación).

Instalación y configuración mínima

pip install drf-spectacular

En settings.py:

INSTALLED_APPS = [
    ...
    'rest_framework',
    'drf_spectacular',
]

REST_FRAMEWORK = {
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}

SPECTACULAR_SETTINGS = {
    'TITLE': 'Mi API de Pedidos',
    'DESCRIPTION': 'API REST para gestionar pedidos, clientes y productos.',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
    'SWAGGER_UI_DIST': 'SIDECAR',
    'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
    'REDOC_DIST': 'SIDECAR',
}

En urls.py del proyecto:

from drf_spectacular.views import (
    SpectacularAPIView,
    SpectacularSwaggerView,
    SpectacularRedocView,
)

urlpatterns = [
    ...
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
    path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]

Con solo eso, visitando /api/docs/ ya tienes Swagger UI funcional con todos tus ViewSets documentados automáticamente. SpectacularAPIView devuelve el JSON OpenAPI crudo, útil para clientes como openapi-generator que generan SDKs en otros lenguajes.

Schema automático a partir de serializers

drf-spectacular analiza tus ModelSerializer y genera los esquemas de request/response sin esfuerzo:

# serializers.py
class ProductoSerializer(serializers.ModelSerializer):
    class Meta:
        model = Producto
        fields = ['id', 'nombre', 'precio', 'stock', 'categoria']
        read_only_fields = ['id']
# views.py
class ProductoViewSet(viewsets.ModelViewSet):
    queryset = Producto.objects.all()
    serializer_class = ProductoSerializer

En Swagger UI aparecen automáticamente:

  • GET /api/productos/ con respuesta [ProductoSerializer] (paginated).
  • POST /api/productos/ con body ProductoSerializer y respuesta 201.
  • GET /api/productos/{id}/ con respuesta ProductoSerializer o 404.
  • PUT /api/productos/{id}/ y PATCH /api/productos/{id}/.
  • DELETE /api/productos/{id}/ con respuesta 204.

Personalizar con @extend_schema

El decorador @extend_schema permite sobrescribir cualquier aspecto del schema generado:

from drf_spectacular.utils import extend_schema, OpenApiExample, OpenApiParameter

class ProductoViewSet(viewsets.ModelViewSet):
    queryset = Producto.objects.all()
    serializer_class = ProductoSerializer

    @extend_schema(
        summary="Listar productos activos",
        description="Devuelve los productos con stock mayor que 0, "
                    "filtrables por categoría y ordenables por precio.",
        tags=['Productos'],
        parameters=[
            OpenApiParameter(
                name='categoria_id',
                type=int,
                location=OpenApiParameter.QUERY,
                description='Filtrar por ID de categoría',
                required=False,
            ),
            OpenApiParameter(
                name='ordering',
                type=str,
                location=OpenApiParameter.QUERY,
                enum=['precio', '-precio', 'nombre', '-nombre'],
                required=False,
            ),
        ],
        responses={200: ProductoSerializer(many=True)},
        examples=[
            OpenApiExample(
                'Ejemplo de producto',
                value={'id': 1, 'nombre': 'Laptop', 'precio': 1200, 'stock': 5, 'categoria': 2},
                response_only=True,
            ),
        ],
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

El resultado: Swagger UI muestra un endpoint con descripción clara, parámetros tipados con dropdown para ordering, y un ejemplo de respuesta.

Documentar acciones personalizadas (@action)

from rest_framework.decorators import action
from rest_framework.response import Response

class ProductoViewSet(viewsets.ModelViewSet):
    ...

    @extend_schema(
        summary="Actualizar stock",
        request={'application/json': {
            'type': 'object',
            'properties': {'cantidad': {'type': 'integer', 'example': 10}},
            'required': ['cantidad'],
        }},
        responses={200: ProductoSerializer, 400: {'description': 'Cantidad inválida'}},
    )
    @action(detail=True, methods=['post'])
    def actualizar_stock(self, request, pk=None):
        producto = self.get_object()
        cantidad = request.data.get('cantidad')
        if cantidad is None or not isinstance(cantidad, int):
            return Response({'error': 'cantidad debe ser entero'}, status=400)
        producto.stock += cantidad
        producto.save()
        return Response(ProductoSerializer(producto).data)

Ejemplos en el body del request

OpenApiExample permite mostrar distintos ejemplos en Swagger UI, útil para APIs con muchos casos:

@extend_schema(
    examples=[
        OpenApiExample(
            'Producto físico',
            value={'nombre': 'Laptop', 'precio': 1200, 'stock': 5, 'tipo': 'fisico'},
            request_only=True,
        ),
        OpenApiExample(
            'Producto digital',
            value={'nombre': 'Ebook', 'precio': 20, 'stock': -1, 'tipo': 'digital'},
            request_only=True,
        ),
    ],
)
def create(self, request, *args, **kwargs):
    return super().create(request, *args, **kwargs)

Polimorfismo: varios serializers de respuesta

Cuando un endpoint puede devolver distintos tipos según el caso, se usa PolymorphicProxySerializer:

from drf_spectacular.utils import PolymorphicProxySerializer

class ProductoFisicoSerializer(serializers.ModelSerializer): ...
class ProductoDigitalSerializer(serializers.ModelSerializer): ...

@extend_schema(
    responses={200: PolymorphicProxySerializer(
        component_name='ProductoUnion',
        serializers=[ProductoFisicoSerializer, ProductoDigitalSerializer],
        resource_type_field_name='tipo',
    )},
)
def retrieve(self, request, *args, **kwargs):
    ...

Autenticación en el schema

drf-spectacular detecta las clases de autenticación de DRF y las documenta automáticamente. Si usas JWT con simplejwt:

SPECTACULAR_SETTINGS = {
    ...
    'SWAGGER_UI_SETTINGS': {
        'deepLinking': True,
        'persistAuthorization': True,
        'displayOperationId': True,
    },
    'SERVERS': [
        {'url': 'https://api.miempresa.com', 'description': 'Producción'},
        {'url': 'http://localhost:8000', 'description': 'Local'},
    ],
}

En Swagger UI aparece el botón Authorize donde el usuario puede pegar su token y probar endpoints autenticados.

Integración con drf-nested-routers y paginación

Las dependencias con plugins comunes de DRF se detectan automáticamente:

  • Paginación (PageNumberPagination, LimitOffsetPagination, CursorPagination): se documentan los parámetros page, page_size, limit, offset, cursor.
  • Filtros (DjangoFilterBackend, SearchFilter, OrderingFilter): se documentan los query params.
  • Permisos: se listan en security.

Excluir vistas internas

Hay endpoints que no deben aparecer en la documentación pública (health checks, métricas):

@extend_schema(exclude=True)
class HealthCheckView(APIView):
    def get(self, request):
        return Response({'status': 'ok'})

Validar el schema en CI

En tu pipeline, puedes detectar cambios accidentales al schema:

# Generar el schema actual
python manage.py spectacular --color --file schema.yml

# Comprobar con el versionado
git diff --exit-code schema.yml
# Si hay diff, el schema cambió. Documentar el cambio en el changelog.

O mejor, usar spectacular-check en pre-commit para garantizar que siempre está al día.

Generar SDKs de cliente

El JSON OpenAPI producido por SpectacularAPIView es compatible con openapi-generator. Puedes regalar a tus integradores un SDK en Python, TypeScript, Go, Java, etc:

openapi-generator generate \
    -i http://localhost:8000/api/schema/ \
    -g typescript-axios \
    -o ./clients/typescript

Esto genera un cliente TypeScript con typing completo, métodos por endpoint y manejo de errores. Los integradores B2B lo adorarán.

Patrón recomendado

  1. Instalar drf-spectacular y configurar los 3 endpoints al inicio del proyecto.
  2. Aceptar el schema automático como default. No sobrescribir nada salvo casos especiales.
  3. Solo usar @extend_schema para:
    • Añadir descripciones y tags útiles.
    • Documentar acciones personalizadas con @action.
    • Excluir vistas internas.
    • Casos polimórficos o request/response no-triviales.
  4. Servir Swagger UI protegido (IsAdminUser) en producción, o accesible solo dentro de la VPN.
  5. En CI, comprobar que el schema no cambia inesperadamente.

Con esto, tu API DRF tiene documentación profesional auto-actualizada sin esfuerzo continuo.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Django

Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Instalar drf-spectacular y configurar el schema OpenAPI 3.1 en settings.py. Registrar las rutas de Swagger UI y Redoc. Personalizar operaciones con @extend_schema (summary, description, tags, request, response, examples). Documentar ejemplos con OpenApiExample. Declarar parámetros de query con OpenApiParameter. Gestionar serializers polimórficos con PolymorphicProxySerializer.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje