Portada de UrsaKit para una guía sobre cómo elegir códigos de estado HTTP en APIs REST

Casi todos los errores de API que sobreviven a la revisión no son errores de lógica: son errores de código de estado. Un handler devuelve 200 con un cuerpo { "error": ... } y, de repente, el cliente móvil muestra una pantalla de éxito, la CDN cachea el fallo diez minutos y la biblioteca de reintentos nunca se activa. La línea de estado es la parte que lee la infraestructura; el cuerpo es lo que lee tu aplicación.

Esta guía recorre los códigos entre los que de verdad eliges, en el orden en que decides. Puedes consultar cualquier código en la referencia de códigos de estado HTTP de UrsaKit, que enlaza cada uno con su RFC.

01El código de estado es un contrato, no una etiqueta

Un código de estado tiene tres lectores, y solo uno es tu aplicación:

  • Las cachés deciden si almacenan la respuesta según 2xx, 4xx o 5xx y cabeceras como Cache-Control.
  • Los proxies y pasarelas tratan un 5xx como fallo del origen, lo que puede abrir un cortacircuitos.
  • Las bibliotecas cliente deciden si reintentan, refrescan un token o muestran un error al usuario.
HTTP/1.1 201 Created
Location: /v1/orders/8f14e45
Content-Type: application/json

{ "id": "8f14e45", "status": "pending" }

Nadie analiza esa línea por diversión: la analiza porque la lógica de reintentos depende de ella.

022xx: lo que el cliente puede dar por hecho

CódigoÚsalo paraCuidado con
200 OKLectura, actualización o acción correcta que devuelve cuerpoNo lo uses para un fallo
201 CreatedUn POST que creó un recursoIncluye Location con la URL nueva
202 AcceptedTrabajo en cola sin terminar (jobs, exportaciones, webhooks)Devuelve una URL de estado que el cliente pueda consultar
204 No ContentÉxito sin nada que devolver (DELETE, PUT sin cuerpo)El cuerpo debe estar vacío
206 Partial ContentPeticiones de rango para mediosRequiere Content-Range

La distinción que merece la pena acertar es 200 frente a 202. Si el cliente tiene que consultar el estado, la petición no ha terminado, y 202 lo dice antes de que el cliente se pregunte si la escritura se perdió.

033xx: redirecciones y caché

CódigoSignificadoMétodo tras la redirección
301 Moved PermanentlyMudanza permanenteLos navegadores pueden cambiar POST por GET
302 FoundMudanza temporalLos navegadores pueden cambiar POST por GET
307 Temporary RedirectMudanza temporalMétodo y cuerpo se conservan
308 Permanent RedirectMudanza permanenteMétodo y cuerpo se conservan
304 Not ModifiedLa petición condicional coincideSin cuerpo, reutiliza la copia en caché

Para endpoints de API, prefiere 307 o 308: son los dos códigos que garantizan que el método y el cuerpo sobreviven al salto. 301 y 302 tienen una larga historia de ser reescritos a GET por los clientes.

044xx: el problema es la petición del cliente

Aquí viven casi todas las decisiones de diseño, porque "petición incorrecta" es una familia, no un código.

CódigoÚsalo cuandoQué corrige el cliente
400 Bad RequestLa petición está mal formada: JSON roto, tipos erróneos, campo requerido ausenteCorrige el cuerpo
401 UnauthorizedSin credenciales, o token caducado o inválidoVuelve a autenticarse; debe enviar WWW-Authenticate
403 ForbiddenAutenticado, pero sin permisoPide acceso; no reintentes igual
404 Not FoundEl recurso no existe o no debe revelarseDeja de reintentar
405 Method Not AllowedLa ruta existe, pero no para ese métodoUsa otro método; debe enviar Allow
406 Not AcceptableNinguna representación encaja con AcceptPide un tipo soportado
409 ConflictConflicto de estado: clave duplicada, versión desfasadaRelee y reintenta con el estado nuevo
410 GoneEl recurso existió y ya no estáQuítalo de marcadores e índices
412 Precondition FailedFalló If-Match o If-Unmodified-SinceVuelve a obtener y reintenta
413 Content Too LargeLa subida supera el límiteEnvía menos contenido
415 Unsupported Media TypeEl Content-Type no se aceptaEnvía JSON, no formulario
422 Unprocessable ContentSintaxis válida, semántica inválidaCorrige los valores
429 Too Many RequestsSe superó el límite de peticionesEspera Retry-After

Tres de ellos llevan una cabecera obligatoria, y omitirla convierte una respuesta útil en una confusa: 401 necesita WWW-Authenticate, 405 necesita Allow y 429 debería enviar Retry-After.

401 y 403 no son intercambiables.

401 significa "no sé quién eres" y el cliente debe autenticarse. 403 significa "sé quién eres y aun así no puedes" — volver a autenticarse no ayuda, así que un cliente que los trata igual acaba en un bucle de login.

La duda entre 403 y 404 es una decisión de seguridad. Si la existencia del recurso ya es sensible —el pedido de otro inquilino, un documento sin publicar— devuelve 404 para que la respuesta no sirva para enumerar. Si el cliente puede saber que existe pero no tocarlo, 403 es más honesto.

055xx: falló el servidor o sus dependencias

CódigoSignificado¿Reintentable?
500 Internal Server ErrorExcepción no controladaNormalmente no; la misma petición volverá a fallar
501 Not ImplementedEl servidor no soporta el método en absolutoNo
502 Bad GatewayEl upstream devolvió algo inutilizableA veces
503 Service UnavailableSobrecarga o mantenimientoSí, respetando Retry-After
504 Gateway TimeoutEl upstream no respondió a tiempoA veces, con presupuesto de timeout

Devuelve 503 con Retry-After durante mantenimientos y sobrecargas. Sin esa cabecera, los clientes bien comportados recurren al retroceso exponencial, y prácticas como usar HTTP 402 para pagos entre agentes existen precisamente porque las señales legibles por máquina superan a adivinar.

Nunca envuelvas un fallo real en un 200.

Un 200 con un objeto de error rompe la caché, oculta el fallo a la monitorización y desactiva la lógica de reintentos que ya existe en cualquier cliente HTTP. Si la respuesta lleva un error, el código de estado debe decirlo.

06Errores habituales que conviene corregir

ErrorPor qué dueleSolución
200 para todoMonitorización, cachés y reintentos ven éxitoUsa el código que corresponde al resultado
401 para errores de permisoEl cliente borra la sesión y vuelve al loginUsa 403
400 para toda validaciónEl cliente no distingue "arregla la sintaxis" de "arregla los valores"400 sintaxis, 422 semántica
500 para errores del clienteSe avisa a guardia por un fallo del llamanteAsigna los fallos de validación a 4xx
404 para un método incorrectoEl cliente no puede descubrir el método permitido405 con Allow
429 sin Retry-AfterLos clientes adivinan y se agolpanEnvía la cabecera
204 con cuerpoProxies y clientes discrepan del enmarcadoDevuelve 200 o quita el cuerpo

07Decidir con tres preguntas

Cuando un handler necesita un código, pregunta en este orden:

  1. ¿Hizo el servidor lo que se le pidió? Si sí, el código es 2xx: 200, 201, 202 o 204 según si se creó, se encoló o se devolvió algo.
  2. ¿Es culpa del llamante? Petición mal formada es 400; credenciales ausentes o inválidas es 401; autenticado pero sin permiso es 403; recurso inexistente es 404; conflicto de estado es 409; sintaxis válida con valores inválidos es 422; demasiadas peticiones es 429.
  3. ¿Es culpa nuestra? Un bug es 500, un upstream inutilizable es 502, un servicio sobrecargado o en mantenimiento es 503 con Retry-After.

Después revisa los detalles del contrato: Location en 201, Allow en 405, WWW-Authenticate en 401, Retry-After en 429 y 503, y cuerpo vacío en 204.

Usa la referencia de códigos de estado HTTP para confirmar el código antes de publicarlo: la descripción está escrita para quien decide, no para un abogado de la especificación. Y si el cuerpo de error es JSON, pásalo también por el validador JSON: una respuesta de error que no se puede analizar es peor que ninguna respuesta de error.

Preguntas frecuentes

¿Debe una API devolver 404 o 403 para un recurso al que el usuario no accede?

Devuelve 404 cuando revelar que existe ya filtra información, la opción habitual con datos por inquilino. Devuelve 403 cuando el cliente ya sabe que el recurso existe y solo le falta permiso.

¿Es aceptable un 200 con un cuerpo de error?

No. Un 200 dice a cada intermediario, caché y biblioteca de reintentos que la llamada tuvo éxito. El cliente tiene que analizar el cuerpo para descubrir el fallo y la monitorización nunca lo ve.

¿Cuándo uso 422 en lugar de 400?

Usa 400 cuando la petición está mal formada: JSON roto, campo obligatorio ausente, tipo incorrecto. Usa 422 cuando la sintaxis es válida pero la semántica no, como una fecha de fin anterior a la de inicio.

¿Qué diferencia hay entre 401 y 407?

401 significa que la petición no lleva credenciales válidas para el recurso y debe incluir la cabecera WWW-Authenticate. 407 es el equivalente para el proxy y usa Proxy-Authenticate.

¿Qué códigos debería reintentar un cliente?

408, 425, 429 y 503 son reintentables de forma explícita, respetando Retry-After. 500 y 502 suelen indicar un fallo del servidor, así que reintentar repite el mismo error.

¿Puede una respuesta 204 llevar cuerpo?

No. 204 significa sin contenido, así que el cuerpo debe estar vacío. Si necesitas devolver el objeto eliminado, usa 200 con cuerpo.

Volver a los artículos

Artículos relacionados

Herramientas relacionadas