Cómo probar un endpoint de API: DNS, TLS, cabeceras y CORS

Punto clave

«La API está caída» casi nunca es un solo problema. Pruébala en seis capas (DNS, puerto 443, TLS, estado HTTP, CORS, carga útil) y detente en la primera que falle. Los culpables habituales: un registro DNS que nunca se propagó, un certificado que caducó durante la noche, o una cabecera Access-Control-Allow-Origin ausente que rompe las llamadas del navegador mientras curl funciona sin problema.

Compruébelo ahora

Diagrama de las seis capas para probar un endpoint de API público: resolución DNS, puerto 443, certificado TLS, cabeceras HTTP, CORS y carga útil JSON
Diagrama de las seis capas para probar un endpoint de API público: resolución DNS, puerto 443, certificado TLS, cabeceras HTTP, CORS y carga útil JSON
Empiece a leer
  1. Por qué «la API está caída» nunca es un solo problema
  2. Elige un endpoint real que puedas probar sin miedo
  3. Capa 1: ¿se resuelve el nombre de host?
  4. Capa 2: ¿está abierto el puerto 443?
  5. Capa 3: ¿es válido el certificado TLS?
  6. Capa 4: lee las cabeceras de la respuesta
  7. Cómo es una respuesta JSON sana
  8. Capa 5: la cabecera que solo rompe las llamadas del navegador
  9. La trampa del preflight: funciona hasta que añades una cabecera
  10. Capa 6: ¿la carga útil tiene la forma que esperas?
  11. Síntoma → capa → herramienta
  12. Preguntas frecuentes

Por qué «la API está caída» nunca es un solo problema

Cuando falla una llamada a una API, el error que muestra tu cliente suele generarse varias capas por encima de lo que realmente se rompió. Un navegador que informa Failed to fetch no puede decirte si el nombre de host no se resolvió, si se rechazó la conexión TCP, si se rechazó el handshake TLS, o si el servidor devolvió un 200 perfectamente sano que el navegador descartó por falta de una cabecera CORS. Los cuatro casos producen el mismo mensaje.

La solución es dejar de adivinar y recorrer la pila en orden. Cada capa solo tiene sentido si la de abajo funcionó, así que la primera capa que falla es tu bug real; todo lo que viene después es ruido.

Hay seis capas que vale la pena revisar, y juntas llevan unos dos minutos:

  • DNS: ¿el nombre de host se resuelve a una IP?
  • Puerto 443: ¿hay algo escuchando, o la conexión se rechaza o se filtra?
  • TLS: ¿el certificado es válido, no ha caducado y se sirve con la cadena completa?
  • Estado HTTP: ¿qué devuelve realmente el servidor: 200, 401, 429, 503?
  • CORS: ¿la respuesta lleva las cabeceras que un navegador necesita para entregarte el cuerpo?
  • Carga útil: ¿el JSON tiene la forma que espera tu código?

Comprueba ahora el estado y las cabeceras de un endpoint

Elige un endpoint real que puedas probar sin miedo

Para practicar necesitas un endpoint en vivo que devuelva datos reales, no pida clave de API y no te limite por lanzar la misma petición veinte veces. Eso descarta a la mayoría de los candidatos obvios: casi todas las API públicas conocidas te exigen ahora un formulario de registro y un token, lo que significa que una petición fallida siempre podría deberse a tus credenciales y no a la capa que estás probando.

Los ejemplos de abajo usan la API gratuita de horarios de oración de Waqt Azan (مواقيت الصلاة), un servicio árabe de horarios de oración que cubre más de 60 000 lugares en más de 100 países. Sirve bien como endpoint de práctica por tres razones: no hay clave ni cabecera de autenticación, devuelve JSON estructurado en lugar de HTML y define una política CORS permisiva. Así, los comandos curl funcionan tal cual, y el ejemplo con fetch() funciona desde la consola de una página sin una Content-Security-Policy estricta.

El endpoint recibe el slug del país y de la ciudad directamente en la ruta:

bash
# Prayer times for a city — no API key, no headers required
curl -s https://waqtazan.com/api/v1/timings/saudi-arabia/riyadh

# The same data as a subscribable calendar feed
curl -s https://waqtazan.com/api/v1/ical/saudi-arabia/riyadh.ics

Sustitúyelo por el host que realmente estás depurando: las seis capas son idénticas tanto si pruebas una API pública, tu propio servidor de staging o un receptor de webhooks de terceros.

Capa 1: ¿se resuelve el nombre de host?

El DNS es un culpable frecuente. Se cambió un registro y no se propagó, se eliminó un wildcard, o la API vive en un subdominio que nunca se creó.

Resuelve el nombre de host antes que nada. Si este paso no devuelve nada, reiniciar tu aplicación no servirá de nada: tu cliente ni siquiera llega a abrir un socket.

bash
dig +short waqtazan.com A
nslookup waqtazan.com

# Compare what different resolvers see — catches mid-propagation records
dig +short waqtazan.com A @1.1.1.1
dig +short waqtazan.com A @8.8.8.8

Si dos resolutores públicos discrepan justo después de cambiar un registro, todavía se está propagando, y la API parecerá rota de forma intermitente según el resolutor que use cada cliente (un CDN con enrutamiento geográfico puede dar siempre una dirección distinta a cada resolutor, así que compara con el registro que configuraste). Haz una consulta DNS para ver el conjunto completo de registros, o comprueba la propagación en resolutores de todo el mundo con el verificador de propagación DNS justo después de cambiar un registro.

Una respuesta vacía aquí se corresponde directamente con ERR_NAME_NOT_RESOLVED en Chrome y getaddrinfo ENOTFOUND en Node: ERR_NAME_NOT_RESOLVED tiene la lista completa de soluciones.

Una cosa que no debe preocuparte: muchas API están detrás de un CDN, así que el registro A apunta a un borde del CDN y no al servidor de origen. Es lo esperado. Lo que importa es obtener una respuesta, no cuál es la IP.

Capa 2: ¿está abierto el puerto 443?

El nombre de host se resuelve, así que hay una dirección. Ahora averigua si algo escucha en ella. Aquí separas tres fallos muy distintos que los clientes suelen reportar igual:

  • Abierto: el handshake TCP se completa. Pasa a la siguiente capa.
  • Rechazado: el host respondió con un RST inmediato. Hay algo ahí, pero nada está enlazado a ese puerto: normalmente el servicio se cayó, o solo escucha en localhost.
  • Filtrado: la conexión se queda colgada hasta agotar el tiempo. Un firewall o grupo de seguridad descarta paquetes en silencio, por eso tu petición espera un minuto o más antes de fallar (75 segundos en macOS).
bash
# Time-boxed TCP check: -w limits the connect on Linux; on macOS use -G 5 instead
nc -vz -w 5 waqtazan.com 443

# Or with curl: --connect-timeout caps the connect; 000 means no response came back
curl -s -o /dev/null -w '%{http_code}\n' --connect-timeout 5 https://waqtazan.com/

Una conexión rechazada y una filtrada necesitan arreglos completamente distintos, así que conviene saber cuál tienes. El verificador de puertos las distingue y prueba desde fuera de tu red, lo cual importa: un puerto abierto desde la VPN de tu oficina puede estar bloqueado por firewall desde Internet.

Si curl imprime 000 como código de estado, no volvió ninguna respuesta HTTP. Normalmente la petición falló en las capas 1 a 3 (sin respuesta DNS, puerto rechazado o filtrado, handshake TLS fallido), pero un servidor que acepta la conexión y luego la cierra o nunca responde también da 000; añade -v para ver el último paso que funcionó. ERR_CONNECTION_REFUSED cubre en detalle el caso del rechazo.

Capa 3: ¿es válido el certificado TLS?

El puerto 443 está abierto, pero un puerto abierto no significa un endpoint HTTPS que funcione. Los certificados son una avería clásica de la noche a la mañana: caducan en una fecha fija y la automatización de la renovación falla sin hacer ruido.

El certificado debe cumplir tres condiciones a la vez: no estar caducado, que su nombre coincida con el host solicitado y que el servidor envíe la cadena intermedia completa. La tercera es la traicionera: una cadena incompleta suele funcionar en los navegadores, porque Chrome descarga por su cuenta el intermedio que falta y Firefox los precarga, mientras que Node, Python, Java y las compilaciones de curl con OpenSSL fallan (el curl que trae macOS usa el verificador de Apple y puede pasar). Ese es el origen del clásico reporte «funciona en mi navegador pero no en producción».

bash
# Expiry dates and subject
echo | openssl s_client -connect waqtazan.com:443 -servername waqtazan.com 2>/dev/null \
  | openssl x509 -noout -subject -dates

# Full chain as presented by the server
openssl s_client -connect waqtazan.com:443 -servername waqtazan.com -showcerts < /dev/null

Cuenta los certificados en la salida de -showcerts. Un solo certificado suele significar que falta un intermedio. El verificador de certificados SSL informa de la caducidad, la integridad de la cadena y la coincidencia del nombre de host de una sola vez, y qué es una cadena de certificados SSL explica por qué el intermedio importa tanto.

Conserva -servername: OpenSSL 1.1.1 y posteriores rellenan el SNI con el host de -connect, pero el openssl de LibreSSL que trae macOS no lo hace, y ninguna versión lo hace cuando te conectas a una dirección IP. Sin SNI, un hosting compartido te entrega su certificado por defecto o, como Cloudflare, aborta el handshake, y acabas persiguiendo una discrepancia de nombre que no existe.

Capa 4: lee las cabeceras de la respuesta

TLS está bien, así que por fin tienes una conversación HTTP real. Ahora lee lo que el servidor dijo de verdad, en lugar de lo que tu cliente resumió por ti. Imprime solo las cabeceras: el cuerpo se descarga igualmente y se tira, así que es el mismo GET que envía tu cliente.

bash
curl -s -D - -o /dev/null https://waqtazan.com/api/v1/timings/egypt/cairo

Lee primero la línea de estado, luego content-type, luego todo lo demás. Esos dos campos te dicen si la petición tuvo éxito y si el cuerpo es JSON antes de que leas nada más.

Cómo es una respuesta JSON sana

Al ejecutar el comando anterior contra el endpoint de prueba el 27 de octubre de 2026, se devolvieron estas cabeceras, entre otras:

CabeceraValorQué te dice
HTTP/2200La petición tuvo éxito y el servidor habla HTTP/2
content-typeapplication/jsonRecibirás JSON, no una página de error HTML
content-length567Una carga útil real, no un cuerpo vacío
access-control-allow-origin*Los navegadores pueden leer esta respuesta
servercloudflareHay un CDN delante del origen
alt-svch3=":443"Se anuncia HTTP/3 para las peticiones siguientes

Capa 5: la cabecera que solo rompe las llamadas del navegador

En esta capa se van las horas, porque es donde curl y el navegador realmente discrepan. CORS lo aplica el navegador, no el servidor. Si falta Access-Control-Allow-Origin, el servidor sigue devolviendo un 200 completo y correcto: el navegador simplemente se niega a entregar el cuerpo a tu JavaScript.

Así que el síntoma es este: curl funciona perfectamente, Postman funciona perfectamente, y la consola del navegador dice que la petición fue bloqueada por la política CORS. No pasa nada en el servidor. A la respuesta solo le falta una cabecera.

El endpoint de prueba devuelve access-control-allow-origin: *, y por eso un fetch() simple desde cualquier origen funciona:

javascript
// Run it in the console of a tab on a site without a strict CSP, such as example.com
// (this page's own CSP blocks it). It works there because ACAO is set to *
const r = await fetch('https://waqtazan.com/api/v1/timings/egypt/cairo')
console.log(await r.json())

La trampa del preflight: funciona hasta que añades una cabecera

Hay una segunda trampa, y este endpoint la demuestra muy bien. Una petición simple (un GET sin cabeceras personalizadas) funciona. Pero en cuanto añades una cabecera personalizada, o haces POST con Content-Type: application/json, el navegador envía primero un preflight OPTIONS, y la respuesta al preflight debe llevar sus propias cabeceras CORS (MDN).

Prueba el preflight por separado, porque es una respuesta distinta, de una ruta de código distinta:

bash
curl -s -D - -o /dev/null -X OPTIONS \
  -H 'Origin: https://example.com' \
  -H 'Access-Control-Request-Method: GET' \
  https://waqtazan.com/api/v1/timings/egypt/cairo

A 27 de octubre de 2026, ese preflight devuelve un 200 con content-type: text/html y ninguna cabecera access-control-allow-origin, así que una petición con preflight desde un navegador sería bloqueada, aunque el GET simple funcione bien.

Esa es la trampa exacta: tu integración funciona en desarrollo, añades una cabecera Authorization o X-Request-Id, y se rompe sin ningún cambio en el servidor. Si solo necesitas datos públicos, mantén la petición simple y seguirá funcionando.

Capa 6: ¿la carga útil tiene la forma que esperas?

La última capa es la que de verdad le importa a tu código. Un 200 con JSON válido todavía puede romperte si se renombró un campo, un tipo pasó de número a cadena, o una marca de tiempo llega en un formato que no interpretas.

Mira la respuesta real antes de escribir el parser. Este endpoint devuelve tanto horas formateadas para mostrar como marcas de tiempo ISO, un patrón que vale la pena copiar: las cadenas localizadas son para las personas, los valores ISO son para el código:

json
{
  "city": { "slug": "riyadh", "name_ar": "الرياض", "name_en": "Riyadh" },
  "country": "saudi-arabia",
  "date": "2026-10-26",
  "times":    { "fajr": "4:38 ص", "dhuhr": "11:37 ص", "maghrib": "5:17 م" },
  "timesISO": { "fajr": "2026-10-26T01:38:00.000Z", "dhuhr": "2026-10-26T08:37:00.000Z" },
  "method": "umm_al_qura"
}

Parsea siempre el campo legible por máquina. Aquí eso significa timesISO, nunca times: las cadenas para mostrar están localizadas y cambiarán de formato en cuanto cambie la configuración regional.

Pasa la respuesta por jq para inspeccionar la estructura rápidamente: curl -s <url> | jq keys lista los campos de primer nivel, y jq '.times' profundiza sin recorrer el cuerpo en bruto.

Síntoma → capa → herramienta

Usa esta tabla para ir directo a la capa que importa, en lugar de recorrer las seis cada vez:

Lo que vesCapa que fallaCompruébalo con
ERR_NAME_NOT_RESOLVED, ENOTFOUND1 — DNSConsulta DNS
ECONNREFUSED, fallo instantáneo2 — PuertoVerificador de puertos
La petición se cuelga y luego agota el tiempo2 — Puerto filtradoVerificador de puertos
CERT_HAS_EXPIRED, UNABLE_TO_VERIFY_LEAF_SIGNATURE3 — TLSVerificador SSL
Funciona en el navegador, falla en curl o Node3 — Cadena incompletaVerificador SSL
401, 429, 5034 — Estado HTTPVerificador de Cabeceras HTTP
Funciona en curl, bloqueado en el navegador5 — CORSVerificador de Cabeceras HTTP
Unexpected token '<', "<!DOCTYPE "... is not valid JSON4 — Página de error HTMLVerificador de Cabeceras HTTP
Campos que faltan o de tipo incorrecto6 — Carga útilcurl … | jq

La latencia queda un poco fuera de esta escalera. Si todas las capas pasan pero las respuestas son lentas, mide el viaje de ida y vuelta al host con ping y comprueba si la propia resolución de nombres es el cuello de botella con una prueba de velocidad DNS.

Comprueba las cabeceras de respuesta reales de cualquier endpoint

Mira el código de estado, el content-type y las cabeceras CORS exactos que devuelve una API, solicitados desde el servidor, así que ninguna caché del navegador ni extensión puede alterar el resultado. Gratis, sin registro.

Fuentes

  1. Cross-Origin Resource Sharing (CORS), MDN
  2. Fetch Standard: CORS-safelisted request headers, WHATWG

Preguntas frecuentes

¿Cómo probar un endpoint de API sin Postman?

curl cubre todo lo que hace Postman para pruebas básicas. Usa curl -s -D - -o /dev/null <url> para ver solo las cabeceras de respuesta, curl -s <url> | jq para inspeccionar el cuerpo JSON, y curl -s -o /dev/null -w '%{http_code}' para imprimir solo el código de estado. Para una comprobación rápida sin terminal, un verificador de cabeceras HTTP en línea devuelve la misma información desde un navegador.

¿Por qué mi API funciona en curl pero falla en el navegador?

Casi siempre es CORS. El servidor devuelve una respuesta válida en ambos casos, pero los navegadores se niegan a exponer el cuerpo a JavaScript a menos que la respuesta lleve una cabecera Access-Control-Allow-Origin que permita tu origen. curl ignora CORS por completo, porque CORS es una política de seguridad del navegador, no del servidor. Si falta la cabecera, el servidor tiene que enviarla: no hay solución del lado del cliente.

¿Qué significa que curl devuelva el código de estado 000?

Significa que nunca se recibió una respuesta HTTP, así que no hay código de estado que mostrar. Normalmente el fallo ocurrió antes de HTTP: el DNS no resolvió, la conexión TCP fue rechazada o filtrada, o falló el handshake TLS. Un servidor que acepta la conexión y luego la cierra o nunca responde también da 000, así que vuelve a ejecutar el comando con -v para ver el último paso que funcionó.

¿Cómo saber si una API está caída para todos o solo para mí?

Prueba desde fuera de tu propia red. Un verificador de puertos y una consulta DNS que se ejecutan en el servidor eliminan de la ecuación tu resolutor local, la VPN, el firewall corporativo y la caché del navegador. Si el endpoint responde desde un verificador externo pero no desde tu máquina, el problema es local: normalmente la caché DNS o una regla de firewall.

¿Por qué una API devuelve 200 pero mi parser JSON lanza un error?

Casi seguro estás recibiendo HTML con estado 200. Los portales cautivos, los proxies y algunas páginas de error devuelven un código de éxito con un cuerpo HTML. Revisa la cabecera content-type: si dice text/html en lugar de application/json, el cuerpo es una página web y el error de parseo es un síntoma, no la causa.

¿Qué es un preflight CORS y cuándo ocurre?

Un preflight es una petición OPTIONS automática que el navegador envía antes de la real siempre que la petición no es «simple»: usa un método distinto de GET, HEAD o POST, define cabeceras personalizadas como Authorization, o envía un Content-Type distinto de datos de formulario o texto plano (application/json cuenta). La respuesta al preflight debe incluir a su vez las cabeceras CORS. Por eso una integración puede funcionar sin problema hasta que añades una cabecera de autenticación y entonces se rompe sin cambios en el servidor.

¿Debo probar el DNS de la API con mi propio resolutor o con uno público?

Con ambos, y compara. El resolutor de tu proveedor puede servir un registro obsoleto en caché mucho después de que cambiara el registro autoritativo. Consultar 1.1.1.1 y 8.8.8.8 junto a tu resolutor por defecto muestra si estás ante un retraso de propagación o ante un registro realmente ausente.

¿Un puerto 443 abierto significa que el endpoint HTTPS funciona?

No. Un puerto abierto solo confirma que algo aceptó una conexión TCP. El handshake TLS todavía puede fallar porque el certificado caducó, el nombre de host no coincide o la cadena intermedia está incompleta. Comprueba el certificado por separado después de confirmar que el puerto está abierto.

Sobre el autor

Shaik Vahid

Fundador de DNS Robot. Vahid pasó años en el soporte de alojamiento web resolviendo dominios, registros DNS, certificados SSL y la entrega de correo para clientes. Desarrolla las herramientas de DNS Robot y escribe estas guías a partir de los errores que la gente encuentra de verdad.

Verificador de encabezados HTTP

Analiza cabeceras de respuesta HTTP y clasificación de cabeceras de seguridad