Leer una respuesta
Códigos de estado, cabeceras de respuesta y el cuerpo.
Cada petición recibe exactamente una respuesta (salvo que se corte la conexión, que es otro tipo de respuesta y se trata en una lección posterior). Una respuesta tiene tres partes: una línea de estado, cabeceras y un cuerpo. Quien empieza a probar tiende a mirar solo el cuerpo, porque ahí están los datos. La mayoría de los bugs que se le escapan están en las otras dos partes.
Cómo es una respuesta
Como la petición, una respuesta HTTP/1.1 es texto plano. Esta es la respuesta a GET /api/health, tal como la muestra curl con la opción -i (que imprime las cabeceras junto con el cuerpo). Se han quitado algunas cabeceras de infraestructura.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
x-powered-by: TestingAPIs
{"status":"healthy","uptimeSeconds":3542,"timestamp":"2026-09-30T18:28:12Z"}La primera línea es la línea de estado: la versión del protocolo, un código de estado de tres cifras y una breve frase de motivo. Después vienen las cabeceras, una por línea, luego una línea vacía y luego el cuerpo. La frase de motivo es solo para personas. Los clientes y los tests deben mirar el número. HTTP/2 elimina la frase de motivo por completo, así que un test que busque el texto "OK" puede romperse cuando el servidor se actualice.
Clases de códigos de estado
La primera cifra del código de estado te dice la clase de la respuesta. A menudo puedes juzgar un resultado solo por esa cifra.
| Clase | Significado | De quién es la culpa |
|---|---|---|
| 1xx | Informativa. La petición sigue en curso. | De nadie. Rara vez las ves directamente. |
| 2xx | Éxito. El servidor hizo lo que pediste. | De nadie. |
| 3xx | Redirección. Mira en otro sitio. | De nadie. El cliente tiene que continuar. |
| 4xx | Error del cliente. La petición estaba mal. | Del cliente: datos incorrectos, sin permiso, URL equivocada. |
| 5xx | Error del servidor. La petición quizá estaba bien. | Del servidor o de algo que hay detrás. |
Esa última columna es la razón por la que los códigos de estado importan tanto en el testing. Un 4xx dice "arregla tu petición". Un 5xx dice "algo se rompió por nuestro lado". Si envías basura y recibes un 500, es un bug aunque la petición fuera mala, porque el servidor se cayó donde debería haber rechazado la entrada con educación con un 400. Encontrar esos 500 es una de las cosas más productivas que puede hacer alguien que empieza a probar.
Los códigos que más verás
| Código | Nombre | Uso típico |
|---|---|---|
| 200 | OK | La petición funcionó y el cuerpo trae el resultado. |
| 201 | Created | Un POST creó un registro nuevo. |
| 204 | No Content | Funcionó y a propósito no hay cuerpo. Habitual tras un DELETE. |
| 301 / 302 | Moved Permanently / Found | El recurso está en la URL de la cabecera Location. |
| 304 | Not Modified | Tu copia en caché sigue al día. Se trata con la caché. |
| 400 | Bad Request | La petición está mal formada o un valor no es válido. |
| 401 | Unauthorized | Sin credenciales, o con credenciales erróneas. Pese al nombre, significa no autenticado. |
| 403 | Forbidden | Las credenciales están bien, pero este usuario no puede hacer esto. |
| 404 | Not Found | No hay nada en esta URL, o ningún registro con este ID. |
| 405 | Method Not Allowed | La URL existe, pero no con este método. |
| 409 | Conflict | La petición choca con el estado actual, por ejemplo un email duplicado. |
| 422 | Unprocessable Content | La sintaxis es correcta pero los datos incumplen una regla. |
| 429 | Too Many Requests | Has alcanzado un límite de frecuencia. Ve más despacio. |
| 500 | Internal Server Error | Un error no controlado en el servidor. |
| 502 | Bad Gateway | Un proxy recibió una mala respuesta del servidor que tiene detrás. |
| 503 | Service Unavailable | El servidor está sobrecargado o en mantenimiento. |
Las APIs no se ponen de acuerdo con algunos de estos. Un equipo devuelve 400 para todos los errores de validación y otro usa 422. Algunas devuelven 200 para un borrado, con un mensaje en el cuerpo; otras, 204 sin nada. Ninguna está mal. Lo que compruebas es que la API coincida con su propia documentación y sea coherente de un endpoint a otro.
Probar códigos de estado
/api/status/{code} responde con el código que pongas en la ruta. Es útil para ver cómo se comporta una herramienta o una librería cliente cuando recibe un estado concreto. Prueba unos cuantos:
/api/status/201/api/status/503Desde curl, el 503 se ve así:
HTTP/1.1 503 Service Unavailable
Content-Type: application/json; charset=utf-8
retry-after: 5
{
"status": 503,
"statusText": "Service Unavailable",
"category": "server-error",
"message": "This is a 503 response. Use /api/status/{code} to request any status."
}Fíjate en la cabecera retry-after: 5. Un 503 o un 429 suele decirle al cliente cuántos segundos esperar antes de volver a intentarlo, y un cliente que se porta bien lo respeta. Compruebas que la cabecera está, no solo el estado.
Los códigos fuera del rango válido se rechazan. /api/status/999 vuelve como un 400 con el mensaje Status code must be between 200 and 599 (1xx cannot be sent as a final response). Y /api/status/abc devuelve un 404 con el cuerpo vacío, porque abc ni siquiera encaja con la ruta. Dos entradas malas, dos tipos de fallo distintos. Verás este patrón a menudo: un valor del tipo equivocado tiende a fallar en la capa de enrutado, antes de que llegue a ejecutarse el código de la aplicación.
Redirecciones
Una respuesta 3xx normalmente no tiene un cuerpo útil. Lo importante es la cabecera Location, que dice adónde ir después:
HTTP/1.1 302 Found
Content-Type: application/json; charset=utf-8
location: /api/echoLos navegadores y la mayoría de las librerías HTTP siguen las redirecciones automáticamente y solo te muestran la respuesta final. Es cómodo hasta que necesitas probar la redirección en sí. curl no las sigue salvo que añadas -L, lo que lo convierte en la mejor herramienta para esto.
Cabeceras de respuesta
Las cabeceras de respuesta describen el cuerpo y dan instrucciones al cliente. Pide la página 3 de la lista de productos, dos productos por página:
/api/Products?limit=2&page=3HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
x-page: 3
x-per-page: 2
x-total-count: 20
x-total-pages: 10Content-Type dice que el cuerpo es JSON en UTF-8. Las cuatro cabeceras x-page y x-total llevan los detalles de la paginación: hay 20 productos, 10 páginas, y esta es la página 3. El cuerpo es solo un array de dos productos, así que un cliente que ignore estas cabeceras no tiene forma de saber que hay más. Las cabeceras que empiezan por X- son personalizadas, inventadas por la API. Las documenta la API, no el estándar HTTP.
Algunas cabeceras guardan horas como marcas de tiempo Unix: el número de segundos desde el 1 de enero de 1970, en UTC. Las APIs con límite de frecuencia suelen enviar una en x-ratelimit-reset para decir cuándo se renueva tu cupo. Conviértela antes de fiarte de ella: una hora de renovación en el pasado, o dentro de años, es un bug que solo pillas si lees las cabeceras en lugar de saltártelas. (El conversor de marcas de tiempo hace la conversión.)
Quizá esperes una cabecera Content-Length. Esta API casi siempre envía Transfer-Encoding: chunked en su lugar, lo que significa que el cuerpo se envía en trozos y el tamaño total no se anuncia de antemano. Las dos opciones son válidas. Una respuesta tiene una u otra, no las dos.
El cuerpo
El cuerpo son los datos: un producto, una lista, un mensaje de error, una imagen. Content-Type dice cómo leerlo. La mayoría de tus comprobaciones serán sobre el cuerpo, y Leer JSON lo trata en detalle. Antes, dos casos especiales.
Sin cuerpo en absoluto
Un 204 significa éxito sin cuerpo, a propósito. La respuesta tampoco tiene Content-Type, porque no hay nada que describir:
/api/formats/no-contentHTTP/1.1 204 No Content
x-powered-by: TestingAPIsEl código cliente que siempre llama a response.json() fallará aquí, porque una cadena vacía no es JSON válido. Si escribes helpers de test que analizan todas las respuestas, tienen que contemplar un cuerpo vacío.
Cuando las cabeceras mienten
El estado, las cabeceras y el cuerpo los producen partes distintas del servidor, y nada les obliga a coincidir. Esta es una respuesta que dice ser HTML:
/api/formats/wrong-content-typeHTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
{"id":1,"name":"Alice Johnson","email":"alice@example.com","roles":["admin","editor"],"active":true,"score":98.5,"manager":null}El cuerpo es un JSON perfectamente bueno. Una persona que lo leyera nunca notaría un problema. Un navegador intentaría mostrarlo como una página web, y algunas librerías HTTP se negarán a analizarlo como JSON o lo devolverán como cadena. Este endpoint está roto a propósito. En sistemas reales, el mismo bug suele venir de una página de error generada por un proxy o por un valor por defecto del framework. Tu test debería comprobar el Content-Type explícitamente.
También ocurre lo contrario: un estado 200 con un cuerpo que dice {"success": false}. Algunas APIs informan así de todos los errores. Las herramientas de monitorización que cuentan respuestas 5xx nunca ven esos fallos, ni tampoco un test que solo mire el código de estado. Si una API hace esto, tus tests tienen que leer el cuerpo de cada respuesta.
Qué comprobar en cada respuesta
Antes de mirar un solo campo de datos, confirma lo básico. Lleva unos segundos y atrapa una cantidad sorprendente de bugs.
- El código de estado es el que la documentación promete para esta situación.
Content-Typecoincide con el cuerpo que recibiste de verdad.- Las cabeceras que menciona la documentación (paginación,
Location,Retry-After) están y tienen sentido. - El cuerpo se puede analizar y tiene la forma que esperas.
- Las respuestas de error siguen el mismo formato que los demás errores de esta API.
Ejercicio
Para cada petición, apunta el código de estado, el Content-Type y una frase sobre el cuerpo.
/api/status/418. Busca por qué existe este código./api/formats/emptyinicia sesión para verlo. ¿En qué se diferencia de/api/formats/no-contentinicia sesión para verlo? Una devuelve 200 y la otra 204. ¿Cuál crees que gestiona mejor un cliente?/api/Products?limit=2&page=99. No hay página 99. ¿Qué estado y qué cuerpo recibiste, y qué dicen las cabeceras de paginación? ¿Lo habrías diseñado igual?/api/status/list. Busca en la lista un código marcado con"supported": falsee intenta pedirlo.