OpenAPI con Swagger UI y Redoc: guía práctica
Guía práctica para documentar APIs REST con OpenAPI. Genera docs interactivas con Swagger UI y Redoc en Python, JavaScript y Java con linting en CI.
Visión General
La mayoría de las documentaciones de API se pudren en READMEs, páginas de Confluence o hilos de Slack. Cada vez que publicas un cambio, esas páginas ya están desactualizadas. OpenAPI, la especificación que surgió de Swagger, te permite describir endpoints, esquemas, y errores en un solo archivo YAML o JSON. Ese mismo archivo puede impulsar documentación interactiva, SDKs de cliente, y tests de contrato.
Esta guía usa ejemplos en Python con FastAPI, JavaScript con Express y Java con SpringDoc, y repasa los trade-offs de cada uno. También compara Swagger UI y Redoc, y explica cómo evitar que el spec se pudra una vez está en producción. Recursos relacionados: Implementar logging y audit trails de API, Implementar Rate Limiting de APIs con Redis, Paginacion por Cursor con PostgreSQL y Construir notificaciones en tiempo real con WebSockets. Ver también Server-Sent Events con Node.js y Express.
Cuándo Usar
Usa esta receta cuando necesites documentación interactiva que se mantenga sincronizada con el código, cuando quieras generar SDKs de cliente en varios lenguajes, cuando tu equipo construya contract-first, o cuando necesites validar solicitudes entrantes contra un esquema formal.
Omitela si la API es solo para uso interno y tú eres el único consumidor: un README corto probablemente basta. En cuanto un segundo equipo dependa de ella, un contrato escrito empieza a dar frutos.
Solución
Python
from fastapi import FastAPI
app = FastAPI(title="Book API", version="1.0.0")
@app.get("/books/{book_id}", tags=["books"])
def get_book(book_id: int):
"""Retrieve a book by its ID."""
return {"id": book_id, "title": "Clean Code"}
# FastAPI auto-generates /openapi.json and /docs (Swagger UI)
JavaScript
const express = require('express');
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const app = express();
const swaggerDocument = YAML.load('./openapi.yaml');
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
app.listen(3000);
Java
import org.springframework.web.bind.annotation.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@RestController
@RequestMapping("/books")
public class BookController {
@Operation(summary = "Get book by ID", description = "Returns a single book")
@ApiResponse(responseCode = "200", description = "Found the book")
@GetMapping("/{id}")
public Book getBook(@PathVariable Long id) {
return new Book(id, "Clean Code");
}
record Book(Long id, String title) {}
}
// springdoc-openapi auto-generates /v3/api-docs and /swagger-ui/index.html
Explicación
Los equipos generan specs OpenAPI de dos maneras, y la elección correcta depende de quién sea el dueño del contrato.
Con code-first, un único equipo construye la API y deja que FastAPI, SpringDoc o tsoa generen openapi.json a partir de anotaciones o decoradores. El spec se mantiene cerca del código, pero puede filtrar modelos internos si no usas DTOs.
Suelo elegir design-first cuando los equipos de frontend, backend, y móvil necesitan ponerse de acuerdo en un contrato antes de escribir código. Entonces escribimos el YAML o JSON a mano, lo publicamos en SwaggerHub o Stoplight, y generamos stubs y clientes a partir de ese contrato. Ese contrato obliga a decidir de forma explícita campos, errores, y versionado. El riesgo es que, sin tests, el spec puede convertirse en una lista de deseos mientras el código hace otra cosa.
Una vez que existe el spec, impulsa documentación interactiva, un sitio de documentación limpio, y generadores de clientes. Swagger UI permite a los desarrolladores llamar endpoints desde el navegador. Redoc renderiza un sitio de tres paneles. Herramientas como openapi-generator-cli producen clientes tipados en TypeScript, Python, Java, y otros lenguajes.
Variantes
| Herramienta | Lenguaje | Enfoque | Salida |
|---|---|---|---|
| FastAPI | Python | Code-first | /openapi.json + /docs auto-generados |
| Flask-RESTX | Python | Code-first | Swagger UI integrado |
| SpringDoc | Java | Code-first | /v3/api-docs + /swagger-ui.html |
| Express + swagger-ui | JavaScript | Design-first | Servir YAML pre-escrito |
| tsoa | TypeScript | Code-first | Generar spec desde decoradores |
Lo que funciona
- Fija la versión del spec a la versión de la API en el campo
info.version, y documenta deprecaciones condeprecated: truemás un path de reemplazo. - Añade ejemplos a los esquemas de solicitud y respuesta; son la forma más rápida de frenar preguntas de integración antes de que empiecen.
- Agrupa operaciones con tags como
users,ordersoproductspara que Swagger UI y Redoc muestren secciones colapsables. - Incluye respuestas de error reales, no solo una respuesta
200exitosa. Añade400,401,404,409y5xxcon cuerpos problem-detail. - Valida el spec en CI con
npx @redocly/cli lintospectral; un$refroto o unoperationIdausente romperá clientes generados sin avisar.
Errores Comunes
Las herramientas code-first son útiles, pero solo ayudan si expones DTOs, no entidades de base de datos, en el spec. De lo contrario, el spec se desvía del código de formas difíciles de detectar.
Toda operación que requiera auth necesita una entrada security y un componente securitySchemes correspondiente. Olvidar cualquiera de los dos es una forma rápida de publicar un muro de autenticación sin documentar.
Los modelos internos deben mantenerse fuera de components/schemas. Usa DTOs dedicados para solicitud y respuesta.
Los campos nullable también sorprenden: OpenAPI 3.0 usa nullable: true, mientras que 3.1 usa type: [string, null]. Una vez elegido, valida contra un linter.
Por último, evita escribir URLs de servidor directamente en el spec; coloca variables en el array servers, como {serverUrl}, para que staging y producción puedan compartir el mismo spec.
Solución de Problemas
Si Redoc o Swagger UI muestra una página en blanco, probablemente el spec esté malformado. Ejecuta el linter de Redocly para encontrar la línea y la regla exactas.
Si los clientes generados no compilan, busca valores operationId duplicados, palabras reservadas en nombres de esquema y valores enum que no sean identificadores válidos en el lenguaje destino.
Si el spec no coincide con el comportamiento desplegado, añade tests de contrato con Schemathesis o Pact en CI para que el desfase del spec rompa el build antes de llegar a los usuarios.
Si los ejemplos en Swagger UI se ven mal, asegúrate de que el campo example esté al nivel correcto del schema, y de que los ejemplos de arrays usen items.example en su lugar.
Los specs grandes ralentizan la página de documentación: divídelo con punteros $ref y empaquétalo con redocly bundle antes de mostrarlo.
Lectura Adicional
- La OpenAPI Specification (latest) es la referencia oficial para nombres de campos, tipos, y diferencias de versión.
- La documentación de Redocly CLI cubre la validación, empaquetado, y publicación de specs OpenAPI.
- La documentación de FastAPI sobre OpenAPI explica cómo FastAPI genera
/openapi.jsony/docs. - En Spring Boot, Springdoc OpenAPI cubre las anotaciones y las personalizaciones comunes.
Notas de Producción
- Versiona el spec en el control de código fuente y etiqueta los releases con la misma versión que la API (
info.versiondebe coincidir con la versión desplegada de la API). - Sirve la documentación desde un artefacto de build separado o una ruta de CDN, de modo que actualizar el spec no requiera desplegar toda la aplicación de nuevo.
- Valida el spec en CI antes de publicar; un
$refroto o unoperationIdausente romperá generadores de clientes y la salida de Redoc. - Monitorea los endpoints de documentación (
/docs,/redoc,/openapi.json) para detectar 4xx/5xx y latencia p99, especialmente después de actualizar el spec.
Puntos Clave
- OpenAPI convierte un único archivo de spec en documentación interactiva, SDKs de cliente y tests de contrato, así que tu documentación se mantiene sincronizada con el código.
- Swagger UI es la mejor opción cuando los desarrolladores necesitan llamar endpoints desde el navegador; Redoc es mejor para una experiencia de lectura limpia y centrada en la documentación.
- FastAPI, Express, y SpringDoc pueden generar el spec automáticamente desde el código, pero los equipos con varios consumidores deberían considerar design-first con un registry compartido.
- Valida el spec en CI con
redocly lintospectralpara detectar referencias rotas,operationIds ausentes y desfase de versiones antes de que lleguen a producción.
Ver También
- API Versioning: estrategias para versionar APIs REST.
- Call REST API: consumir APIs REST desde código cliente.
- GraphQL API: enfoque alternativo de API.
- Handle CORS: configuración de cross-origin resource sharing.
- Handle Errors: patrones estructurados de manejo de errores.
Errores Comunes en Producción
- Dejar que el spec OpenAPI se desfase del API desplegado, de forma que la documentación, los clientes y los tests dejen de coincidir con la realidad.
- Exponer esquemas internos de base de datos en
components/schemasen lugar de DTOs estables. - Saltar el lint del spec en CI y publicar referencias
$refrotas o inválidas. - Usar seguridad
apiKeypara tokens JWT tipo bearer en lugar dehttpconscheme: bearer. - Olvidar versionar el spec junto con la API, o eliminar paths deprecados demasiado pronto.
- Exponer los endpoints
/docsy/redocgenerados en APIs internas sin control de acceso. - Dar por sentado que un cliente generado funcionará sin verificar compatibilidad con tu versión de OpenAPI y extensiones.
Preguntas frecuentes
¿Debería usar code-first o design-first?
Si la API es interna y solo la consume tu equipo, empieza con code-first. Herramientas como FastAPI, SpringDoc, o tsoa pueden derivar el spec de tus anotaciones, así que el contrato se mantiene cerca del código.
@app.get("/books/{book_id}")
def get_book(book_id: int):
...
Si los equipos de frontend, móvil, backend o socios externos necesitan acordar el contrato primero, escribe el YAML de OpenAPI y publícalo en SwaggerHub o Stoplight antes de generar stubs.
openapi-generator-cli generate -i openapi.yaml -g python-fastapi
El riesgo de design-first es el desfase: el spec se convierte en una ilusión mientras el código hace otra cosa. Evítalo con tests de contrato (Schemathesis, Pact) en CI. El riesgo de code-first es filtrar modelos internos; evítalo retornando DTOs, no entidades de base de datos.
¿Cómo mantengo la documentación sincronizada con el código desplegado?
Genera el spec en CI desde el código, publícalo en un registry como SwaggerHub o Stoplight, y apunta la documentación desplegada a la última versión. En GitHub Actions.
name: Generate OpenAPI Spec
on: push
jobs:
spec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python -m app.main --export-openapi > openapi.json
- run: npx @redocly/cli lint openapi.json
- run: npx @redocly/cli build-docs openapi.json -o docs/
Ejecuta npx @redocly/cli lint openapi.yaml en cada PR para detectar violaciones de esquema, respuestas faltantes y referencias rotas. Publícalo como artefacto del build, despliega la documentación junto con la API y luego verifica la API contra el spec ejecutando el siguiente comando:
schemathesis run openapi.json --base-url http://localhost:8000
¿Cuál es la mejor forma de documentar autenticación y autorización en OpenAPI?
Describe la autenticación en components/securitySchemes y luego aplícala a cada operación. El ejemplo siguiente muestra un esquema Bearer JWT.
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
Para las API keys, usa type: apiKey e indica en qué header o query las esperas, como en el ejemplo siguiente.
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
OAuth2 requiere declarar el flujo y los scopes permitidos.
components:
securitySchemes:
OAuth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://api.example.com/oauth/authorize
tokenUrl: https://api.example.com/oauth/token
scopes:
read: Read access
write: Write access
Una vez definidos los esquemas, añade la entrada security a nivel de operación.
paths:
/books:
get:
security:
- BearerAuth: []
post:
security:
- OAuth2: [write]
Para tokens JWT en OpenAPI 3.1, sigue usando type: http y scheme: bearer. El tipo apiKey todavía es válido, pero no encaja con bearer tokens, porque describe una clave de API personalizada en lugar de un esquema HTTP bearer.
¿Por qué importa el versionado en specs OpenAPI?
La versión del spec va en info.version; elige una estrategia de versionado que los clientes puedan descubrir fácilmente.
info:
title: Book API
version: 2.1.0
Usa versionado semántico: major para cambios incompatibles, minor para endpoints nuevos y patch para correcciones. Si prefieres versionar por URL, incluye la versión en la URL del servidor.
servers:
- url: https://api.example.com/v2
El versionado por header se resuelve con un parámetro adicional.
parameters:
- name: X-API-Version
in: header
required: true
schema:
type: string
default: "2"
Para marcar una operación como deprecada, añade deprecated: true y explica la alternativa en la descripción.
paths:
/books/{id}:
get:
deprecated: true
description: Use /v2/books/{id} instead
Durante los períodos de migración, mantén ambas versiones del spec y deja que el cliente negocie la versión con el header Accept, por ejemplo enviando Accept: application/vnd.api+json;version=2.
¿Puedo convertir Swagger 2.0 a OpenAPI 3.0?
Sí. Puedes usar la CLI swagger2openapi o el conversor integrado de Swagger Editor. La mayoría de herramientas modernas maneja 3.0 de forma nativa. Ejecuta npx swagger2openapi swagger.json -o openapi.json y espera algunos cambios mecánicos. host, basePath y schemes se combinan en un array servers; definitions y responses se mueven a components/schemas y components/responses; y securityDefinitions pasa a ser components/securitySchemes. Los campos globales produces y consumes desaparecen; ahora cada operación declara su negociación de contenido en su propio bloque content. Después, valida el resultado con npx @redocly/cli lint openapi.json. Algunos casos extremos, como type: file pasando a format: binary y collectionFormat convirtiéndose en parámetros style y explode, aún requieren ajustes manuales.
¿Qué cambió entre OpenAPI 3.0 y 3.1?
OpenAPI 3.1 elimina el antiguo flag nullable: true y en su lugar usa type: [string, null]. Además, exclusiveMinimum y exclusiveMaximum pasan de ser booleanos a números que indican el límite excluido.
minimum: 0
exclusiveMinimum: true
En 3.1, eso pasa a ser exclusiveMinimum: 0. Las subidas binarias cambian de format: binary a contentEncoding: binary, los webhooks obtienen un campo webhooks de nivel superior, y los identificadores de licencia usan SPDX. El campo summary dentro de $ref es opcional, y paths puede estar vacío para APIs que solo usen webhooks.
Antes de migrar, valida el spec con redocly lint y convierte con npx @redocly/cli@latest convert openapi.yaml --to 3.1. La mayoría de herramientas soportan 3.1, pero verifica tu generador y parser primero.
¿Qué pasa con las subidas y descargas de archivos en OpenAPI?
Para subidas de archivos en OpenAPI 3.0, usa format: binary en una propiedad de tipo string dentro del cuerpo de una petición multipart/form-data. Si necesitas varios archivos, cambia la propiedad a un array de cadenas binarias.
En OpenAPI 3.1, format: binary se convierte en contentEncoding: binary. Para descargas, la respuesta debería usar el content type application/octet-stream, lo que significa que también debes declarar un esquema de cadena binaria para el cuerpo de la respuesta. Las imágenes funcionan de la misma forma: declara el tipo de contenido, como image/png, y un esquema binario.
Para limitar el tamaño de subida, añade maxLength al campo binario e indica el límite en la descripción, por ejemplo 10485760 bytes para 10 MB.
¿Qué son los esquemas polimórficos y cómo funcionan en OpenAPI?
Los tipos polimórficos se modelan con las keywords oneOf, anyOf y allOf. El ejemplo siguiente es una unión discriminada.
components:
schemas:
Pet:
oneOf:
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Cat'
discriminator:
propertyName: type
mapping:
dog: '#/components/schemas/Dog'
cat: '#/components/schemas/Cat'
Cada subtipo incluye el campo discriminador.
Dog:
type: object
properties:
type:
type: string
enum: [dog]
breed:
type: string
required: [type, breed]
Para aceptar tipos mixtos, usa anyOf en lugar de herencia.
PropertyValue:
anyOf:
- type: string
- type: number
- type: boolean
- type: array
items:
$ref: '#/components/schemas/PropertyValue'
Cuando necesites heredar propiedades sin un discriminador, usa allOf para combinar los esquemas.
Animal:
allOf:
- $ref: '#/components/schemas/BaseEntity'
- type: object
properties:
species:
type: string
¿Por qué usar RFC 7807 Problem Details para respuestas de error?
Para los errores RFC 7807, usa el media type application/problem+json y un schema Problem reutilizable.
components:
schemas:
Problem:
type: object
properties:
type:
type: string
format: uri
default: about:blank
title:
type: string
status:
type: integer
detail:
type: string
instance:
type: string
format: uri
Después referencia ese schema en las respuestas de error mediante $ref en cada endpoint.
responses:
'404':
description: Book not found
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
examples:
not_found:
value:
type: https://api.example.com/errors/not-found
title: Book not found
status: 404
detail: Book with ID 42 not found
instance: /books/42
Documenta los códigos de error comunes, como 400 para errores de validación, 401 para autenticación faltante, 403 para permisos insuficientes, 409 para conflictos, 422 para fallos semánticos y 429 para limitación de frecuencia.
¿Qué herramientas validan specs OpenAPI en CI?
Redocly y Spectral son los linters que más uso. Con Redocly puedes instalar el CLI globalmente (npm install -g @redocly/cli) y ejecutar redocly lint openapi.yaml. A continuación añade un archivo de reglas sencillo para reforzar tus convenciones.
rules:
operation-operationId-unique:
severity: error
operation-summary:
severity: warn
max_length: 50
Spectral funciona de forma similar: instálalo con npm install -g @stoplight/spectral-cli, extiende el ruleset OAS incorporado y personaliza las reglas que te importen. Luego añádelo a GitHub Actions.
- name: Lint OpenAPI
run: npx @redocly/cli lint openapi.yaml
Valida la estructura del spec: busca operationId faltante, destinos $ref no definidos, esquemas de respuesta faltantes y parámetros de path duplicados. Para corregir problemas comunes ejecuta redocly lint --format=json openapi.yaml | jq '.problems[] | select(.ruleId == "operation-summary")' para filtrar reglas específicas.
Recursos Relacionados
Diseño de APIs REST: Lo que funciona
Diseña APIs REST robustas y escalables con métodos HTTP adecuados, códigos de estado, versionado y estrategias de paginación.
RecipeVersionado de APIs
Cómo versionar APIs REST y GraphQL para mantener compatibilidad hacia atrás mientras evolucionas tu interfaz.
RecipeManejar Errores en APIs con RFC 7807
Patrones para un manejo de errores de API consistente y predecible en varios lenguajes y frameworks.
RecipeManejo Correcto de CORS
Cómo configurar headers de Cross-Origin Resource Sharing (CORS) correctamente para APIs, SPAs y funciones serverless sin abrir agujeros de seguridad.
RecipeValidación de Input
Cómo validar input de usuarios de forma segura usando schemas, type checking y sanitización en Python, JavaScript y Java.
RecipeEndpoints de API Idempotentes
Cómo diseñar e implementar endpoints de API idempotentes que manejen retries, requests duplicados y fallas de red sin efectos secundarios.