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.

Respuesta en bruto
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.

ClaseSignificadoDe quién es la culpa
1xxInformativa. La petición sigue en curso.De nadie. Rara vez las ves directamente.
2xxÉxito. El servidor hizo lo que pediste.De nadie.
3xxRedirección. Mira en otro sitio.De nadie. El cliente tiene que continuar.
4xxError del cliente. La petición estaba mal.Del cliente: datos incorrectos, sin permiso, URL equivocada.
5xxError 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ódigoNombreUso típico
200OKLa petición funcionó y el cuerpo trae el resultado.
201CreatedUn POST creó un registro nuevo.
204No ContentFuncionó y a propósito no hay cuerpo. Habitual tras un DELETE.
301 / 302Moved Permanently / FoundEl recurso está en la URL de la cabecera Location.
304Not ModifiedTu copia en caché sigue al día. Se trata con la caché.
400Bad RequestLa petición está mal formada o un valor no es válido.
401UnauthorizedSin credenciales, o con credenciales erróneas. Pese al nombre, significa no autenticado.
403ForbiddenLas credenciales están bien, pero este usuario no puede hacer esto.
404Not FoundNo hay nada en esta URL, o ningún registro con este ID.
405Method Not AllowedLa URL existe, pero no con este método.
409ConflictLa petición choca con el estado actual, por ejemplo un email duplicado.
422Unprocessable ContentLa sintaxis es correcta pero los datos incumplen una regla.
429Too Many RequestsHas alcanzado un límite de frecuencia. Ve más despacio.
500Internal Server ErrorUn error no controlado en el servidor.
502Bad GatewayUn proxy recibió una mala respuesta del servidor que tiene detrás.
503Service UnavailableEl 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:

GET/api/status/201
GET/api/status/503

Desde curl, el 503 se ve así:

Respuesta
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:

Respuesta a GET /api/status/302
HTTP/1.1 302 Found
Content-Type: application/json; charset=utf-8
location: /api/echo

Los 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:

GET/api/Products?limit=2&page=3
Cabeceras de respuesta
HTTP/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: 10

Content-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:

GET/api/formats/no-content
Esta petición usa un endpoint para miembros con sesión iniciada.Iniciar sesiónCrear una cuenta gratuita
Respuesta
HTTP/1.1 204 No Content
x-powered-by: TestingAPIs

El 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:

GET/api/formats/wrong-content-type
Esta petición usa un endpoint para miembros con sesión iniciada.Iniciar sesiónCrear una cuenta gratuita
Respuesta
HTTP/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-Type coincide 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": false e intenta pedirlo.

Practica y ve más allá