Definición
Un request schema es una especificación formal que define qué datos deben enviar los clientes al llamar a un endpoint de API. Describe la estructura de cuerpos de petición (para POST, PUT, PATCH), parámetros de query, headers y parámetros de path. El schema especifica tipos de datos, campos obligatorios, restricciones de validación (como valores mín/máx, patrones de string), y a menudo incluye ejemplos para guiar a los desarrolladores.
Los request schemas sirven como primera línea de defensa contra datos incorrectos. Antes de que tu lógica de negocio se ejecute, antes de que se ejecuten consultas a base de datos, antes de que ocurran cálculos costosos, el API gateway o framework valida peticiones entrantes contra el schema. Las peticiones inválidas se rechazan inmediatamente con mensajes de error claros, protegiendo sistemas backend de datos mal formados y reduciendo tiempo de depuración.
En especificaciones OpenAPI, los request schemas se definen en la sección requestBody para contenido de cuerpo y en parameters para valores de URL/query/header. Estos schemas son tanto documentación (mostrando a desarrolladores qué enviar) como aplicación (validando peticiones reales).
Ejemplo
Endpoint de Registro de Usuario: Una API de registro define un request schema que requiere email (string, formato email), password (string, minLength: 12, pattern con reglas de complejidad), username (string, 3-20 caracteres alfanuméricos), y referralCode opcional (string, exactamente 8 letras mayúsculas). Las peticiones sin email o con contraseñas débiles se rechazan antes de tocar la base de datos.
Checkout E-commerce: El request schema de un endpoint de checkout requiere items (array de objetos con productId y quantity), shippingAddress (objeto con campos obligatorios: street, city, zip, country), paymentMethod (enum: credit_card, paypal, bank_transfer), y condicionalmente requiere cardDetails solo cuando paymentMethod es credit_card. El schema asegura que todos los datos necesarios están presentes antes de procesar el pedido.
API de Búsqueda con Filtros: Un endpoint de búsqueda de productos define schemas de parámetros de query: q (string, minLength: 2, maxLength: 200), category (enum de categorías válidas), minPrice y maxPrice (números, minimum: 0), page (integer, minimum: 1), sort (enum: price_asc, price_desc, relevance). Peticiones con parámetros inválidos (como page: -1 o minPrice: “abc”) se rechazan.
Endpoint de Subida de Archivo: El request schema de una API de subida de documentos especifica que el header Content-Type debe ser multipart/form-data, tamaño máximo de archivo 10MB, tipos de archivo permitidos (pdf, docx, txt vía pattern en extensión de nombre de archivo), y requiere campos de metadata como title y description en los datos del formulario.
Operaciones por Lotes: Un endpoint de actualización masiva de usuarios requiere un array de objetos usuario en el cuerpo de petición, con restricciones de schema: array minItems: 1, maxItems: 100 (previniendo DOS vía lotes enormes), cada objeto usuario requiere id (formato UUID) y campos opcionales a actualizar con sus propias reglas de validación.
Analogía
El Control de Seguridad del Aeropuerto: Antes de que puedas abordar (procesar la petición), seguridad (validación de schema) verifica que tu equipaje de mano (cuerpo de petición) cumple límites de tamaño, no contiene artículos prohibidos (tipos de datos inválidos), y tu identificación (campos obligatorios) está presente y válida. Falla la verificación, y no procedes - arreglas el problema e intentas de nuevo.
El Formulario de Pedido del Restaurante: Al pedir comida, el formulario (request schema) requiere que selecciones un plato principal (campo obligatorio), especifiques cantidad (integer, minimum: 1), opcionalmente agregues acompañamientos (array de opciones válidas), y notes alergias (string, maxLength: 200). La cocina (backend) solo recibe pedidos completos y válidos - los formularios incompletos se te devuelven.
La Solicitud de Préstamo: La solicitud de préstamo de un banco (request schema) requiere comprobante de ingresos (obligatorio, numérico, minimum: 0), historial laboral (array de objetos con campos específicos), puntaje crediticio (integer, rango 300-850), y monto del préstamo (número, dentro de límites de préstamo del banco). Solicitudes sin documentos requeridos o con valores fuera de rango se rechazan inmediatamente, antes de que los suscriptores las revisen.
Ejemplo de Código
# Definición de Request [Schema](https://reference.apios.info/es/terms/schema/) en [OpenAPI 3](https://reference.apios.info/es/terms/openapi-3/).x
paths:
/api/users:
post:
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
- password
- username
properties:
email:
type: string
format: email
description: User's email address
example: [email protected]
password:
type: string
minLength: 12
maxLength: 128
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$"
description: Password with uppercase, lowercase, digit, special char
example: SecureP@ssw0rd!
username:
type: string
minLength: 3
maxLength: 20
pattern: "^[a-zA-Z0-9_]+$"
description: Alphanumeric username with underscores
example: alice_smith
dateOfBirth:
type: string
format: date
description: Birth date (must be 18+ years ago)
example: "1995-06-15"
referralCode:
type: string
pattern: "^[A-Z]{8}$"
description: Optional 8-character uppercase referral code
example: WELCOME2024
additionalProperties: false
responses:
'201':
description: User created successfully
'400':
description: Invalid request data
content:
application/json:
schema:
type: object
properties:
errors:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
example:
errors:
- field: email
message: "must be a valid email address"
- field: password
message: "must contain at least one uppercase letter"
Validando request en Express.js:
const Ajv = require("ajv");
const addFormats = require("ajv-formats");
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
const createUserSchema = {
type: "object",
required: ["email", "password", "username"],
properties: {
email: { type: "string", format: "email" },
password: {
type: "string",
minLength: 12,
maxLength: 128,
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]+$"
},
username: {
type: "string",
minLength: 3,
maxLength: 20,
pattern: "^[a-zA-Z0-9_]+$"
},
dateOfBirth: { type: "string", format: "date" },
referralCode: {
type: "string",
pattern: "^[A-Z]{8}$"
}
},
additionalProperties: false
};
const validate = ajv.compile(createUserSchema);
app.post('/api/users', (req, res) => {
// Validar cuerpo de petición
const valid = validate(req.body);
if (!valid) {
return res.status(400).json({
errors: validate.errors.map(err => ({
field: err.instancePath.substring(1), // remover / inicial
message: err.message
}))
});
}
// Validación adicional de lógica de negocio
const age = calculateAge(req.body.dateOfBirth);
if (age < 18) {
return res.status(400).json({
errors: [{ field: "dateOfBirth", message: "must be 18 or older" }]
});
}
// Proceder con creación de usuario
createUser(req.body);
res.status(201).json({ message: "User created" });
});
function calculateAge(dateString) {
const birthDate = new Date(dateString);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
Diagrama
sequenceDiagram
participant Client
participant Gateway
participant Schema
participant Handler
participant DB
Client->>Gateway: POST /api/users
{email, password, ...}
Gateway->>Schema: Validar cuerpo de petición
alt Petición Inválida
Schema-->>Gateway: Errores de validación
Gateway-->>Client: 400 Bad Request
Errores a nivel de campo
else Petición Válida
Schema-->>Gateway: Válida
Gateway->>Handler: Reenviar petición
Handler->>Handler: Validación lógica de negocio
alt Reglas de negocio fallaron
Handler-->>Gateway: Error de negocio
Gateway-->>Client: 422 Unprocessable Entity
else Todo válido
Handler->>DB: Crear usuario
DB-->>Handler: Usuario creado
Handler-->>Gateway: Éxito
Gateway-->>Client: 201 Created
end
end
Notas de Seguridad
CRÍTICO - …
Configuración y Validación:
- Siempre valida request schemas en el API gateway antes de la lógica de negocio.
- Establece additionalProperties en false para prevenir vulnerabilidades de asignación masiva.
- Usa maxLength en todos los campos string para prevenir denegación de servicio.
- Valida profundidad de objetos anidados para bloquear cargas recursivas.
Monitoreo y Protección:
- Nunca confíes en headers Content-Type proporcionados por clientes sin validación.
- Sanitiza campos basados en patterns que puedan usarse en SQL o comandos de sistema.
- Limita la tasa de peticiones que fallan validación para detectar ataques de sondeo.
- Registra fallos de validación para monitoreo de seguridad.
Mejores Prácticas
- Validar temprano - Rechaza peticiones inválidas en el API gateway, no en lógica de negocio
- Ser específico - Usa validadores de formato (email, uuid, date-time) en lugar de strings genéricos
- Establecer límites - Define minLength, maxLength, minimum, maximum en todos los campos
- Proporcionar ejemplos - Incluye ejemplos realistas en el schema para cada propiedad
- Retornar errores accionables - Mapea cada error de validación al campo específico y restricción violada
- Usar enums para valores fijos - No aceptes strings arbitrarios cuando un conjunto fijo de valores es válido
- Validar profundamente - Verifica objetos anidados y arrays, no solo campos de nivel superior
- Considerar campos opcionales cuidadosamente - Haz campos obligatorios a menos que haya una razón clara para opcionalidad
Errores Comunes
Falta additionalProperties: false: Permitir a clientes enviar campos extra arbitrarios crea vulnerabilidades de asignación masiva y hace riesgosos cambios futuros de schema.
Validación de pattern débil: Usar pattern: “.*” o regex excesivamente permisivo que acepta datos inválidos. Sé estricto desde el principio.
Sin límites de tamaño: Aceptar strings o arrays sin límites habilita ataques DOS vía cargas enormes. Siempre establece maxLength y maxItems.
Validar muy tarde: Ejecutar validación después de que la lógica de negocio empieza, desperdiciando recursos en peticiones inválidas. Valida en el borde.
Mensajes de error genéricos: Retornar “validation failed” en lugar de errores específicos por campo. Los desarrolladores necesitan saber exactamente qué está mal para arreglarlo.
No validar parámetros de query: Solo validar cuerpos de petición mientras se ignoran riesgos de inyección en query string. Schema-valida todas las entradas.
Validación inconsistente: Tener reglas de validación diferentes en schema vs. lógica de negocio vs. restricciones de base de datos. El schema debe ser la fuente de verdad.