Triaje de errores HTTP
Un error HTTP te dice que una solicitud falló. Normalmente no te dice por qué. Concluir "la clave está muerta" a partir de un 403 es una suposición disfrazada de número.
Nacido de un incidente real (2026-07-30): una API de enriquecimiento de contactos de terceros
devolvió 403 error code: 1010 a cada llamada durante seis días. Se interpretó como "clave
no autorizada", "créditos agotados", "cuota mensual alcanzada". Se construyó una solución
de automatización de navegador de 756 líneas basada en esa creencia, y a un cliente casi se le
pidió que revisara un plan que estaba bien. La causa real era un User-Agent faltante, por lo que
el CDN prohibió al cliente en el borde. Un header lo solucionó: 200 OK, datos completos,
y el saldo de crédito había estado allí todo el tiempo.
Ejecuta estos tres controles antes de diagnosticar
1. El control de credencial inválida. Envía la misma solicitud con una credencial deliberadamente basura, y otra vez sin ninguna.
for label, key in [("real", REAL_KEY), ("garbage", "0"*36), ("empty", "")]:
... # misma URL, mismo método, mismos headers
Si los tres devuelven el mismo estado y cuerpo, el error no tiene que ver con tu credencial. No aporta información sobre autorización, cuota o validez. Deja de razonar sobre la clave.
2. El control de ruta sin sentido. Solicita una ruta que ciertamente no existe, y la raíz del sitio.
GET https://api.example.com/definitely-not-a-real-path-12345
GET https://api.example.com/
GET https://www.example.com/ # su sitio de marketing
Si el error de la API también aparece desde el sitio de marketing, estás siendo bloqueado delante de el origen. Nada sobre la llamada a la API ayudará.
3. Lee los headers de respuesta, no solo el cuerpo.
except urllib.error.HTTPError as e:
print(e.code, e.headers.get("server"), e.read()[:200])
server: cloudflare en un error de "auth" es la pista. También lo son cf-ray,
x-amzn-waf, akamai. Esos son infraestructura, no la API.
Firmas conocidas
| Síntoma | Causa real | Solución |
|---|---|---|
403 + cuerpo error code: 1010, server: cloudflare | Cloudflare bloqueó la firma del cliente | Enviar un User-Agent de navegador real |
Mismo error para cada ruta incl. / | Bloqueo de borde/WAF o prohibición de IP | Cambiar UA, luego IP/proxy |
402 | Realmente sin créditos: la solicitud se autenticó | Recargar; la clave está bien |
429 justo después de un éxito | Límite de tasa real | Espaciar solicitudes, añadir retroceso |
400 con un mensaje de campo | Llegaste a la API y la autenticación pasó | Corregir el payload; esto es una buena noticia |
Un error de validación 400 es una señal de éxito para el triaje: prueba que la autenticación fue
aceptada.
Headers por defecto para cualquier llamada a API del lado del servidor
urllib de Python envía User-Agent: Python-urllib/3.x, que muchos WAF bloquean
directamente. Siempre establece:
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36",
"Accept": "application/json",
"Content-Type": "application/json",
}
Luego mide el costo antes de cualquier barrido
Una vez que funcione, no estimes el gasto. Lee el uso antes y después de una llamada, y lee el propio campo de facturación del proveedor en la respuesta.
before = usage(); result = call(); after = usage()
print("charged:", result.get("billing"), "| measured:", before - after)
Qué informar
Indica qué probaste y qué queda desconocido. "Ambas claves 403" no es un hallazgo si una clave vacía devuelve el mismo 403. Di en su lugar: "El 403 no es informativo aquí, una credencial nula también lo devuelve; la causa está antes de la autenticación."