RUNBOOK DE ERRORES
Un runbook para los errores de la API de Pinnacle.
Cada estado que devuelve la API de cuotas de Pinnacle pide una reacción distinta de un poller. Este runbook los clasifica en parar, esperar y reintentar, da la regla de espera de cada uno y dice cuáles deberían despertar a una persona. Las reglas son las mismas con la clave gratuita y con los planes REST de pago.
Cuatro clases de respuesta
Un poller solo necesita cuatro reacciones. La tabla asigna cada estado a una de ellas; todo lo que sigue explica los tiempos.
| Respuesta | Qué significa | Reacción |
|---|---|---|
| 401 | Sin clave, o una clave que la API no reconoce | Parar y avisar a una persona |
| 403 | La clave es conocida, pero el plan no cubre esta llamada | Parar y revisar el plan |
| 429 | La asignación por segundo o diaria se ha agotado | Esperar el Retry-After y reanudar |
| 5xx, timeout, reset | El servicio o la red han fallado por ahora | Reintentar con un backoff acotado |
| 200 con un cuerpo inesperado | Una respuesta que no es el envelope documentado | Tratar como fallo; conservar el último snapshot |
Errores de parada: 401 y 403
Ninguno de los dos mejora con el tiempo, así que reintentarlos en bucle solo añade ruido a los registros y, en una cuenta compartida, al lado del proveedor. Un 401 significa que la solicitud no llevaba una clave utilizable: falta la cabecera, la variable de entorno está vacía en este despliegue, o la clave fue rotada. Comprueba que el poller envía la clave en la cabecera x-portal-apikey y que el valor coincide con el panel de la cuenta.
Un 403 es más sutil: la autenticación funcionó, pero el plan no incluye lo que se pidió. Una clave válida puede seguir sin tener un transporte, un complemento o un endpoint, y la solución está en la cuenta, no en el código. La lista de comprobación de acceso en pnclODDS separa la autenticación del derecho de uso paso a paso. En ambos casos registra el estado y el código de error, nunca la clave ni el cuerpo completo de la respuesta, y avisa a quien gestione la cuenta.
Errores de espera: 429
Un 429 dice que la programación cruzó el techo: 20 solicitudes por minuto o 100 al día con la clave gratuita, 10 o 30 por segundo en los planes REST de pago. La cabecera Retry-After es la autoridad sobre cuánto esperar. Respétala, añade un poco de jitter para que los workers en paralelo no se reanuden al unísono, y comparte el estado del backoff entre workers, ya que diez procesos educados siguen sumando diez veces las solicitudes. La página de límites de tasa tiene el bucle de backoff y la regla de margen que mantiene los 429 como algo raro.
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Un 429 sobre la asignación diaria de la clave gratuita es de otra naturaleza: ningún backoff devuelve el día. Detéctalo por el tamaño del Retry-After y detén el poller hasta que la ventana se reinicie, en lugar de quemar la siguiente hora en llamadas rechazadas.
Errores de reintento: 5xx, timeouts y resets
Son transitorios hasta que se demuestre lo contrario. Reintenta con un retraso que empieza en medio segundo, se duplica cada vez y se detiene en 30 segundos, y abandona después de tres intentos en un ciclo de sondeo. Fallar el ciclo no pasa nada: el siguiente sondeo programado corre de todos modos, y un snapshot con diez segundos de retraso es mejor que un poller atascado en un bucle de reintentos. Sigue sirviendo el último snapshot bueno a tus propios consumidores, con su antigüedad visible, para que un dashboard muestre precios desactualizados en lugar de ninguno.
def poll_once():
delay = 0.5
for attempt in range(3):
try:
res = get(url, timeout=8)
except (Timeout, ConnectionError):
sleep(delay + random() * delay / 2)
delay = min(delay * 2, 30)
continue
if res.status in (401, 403):
raise Stop(res.status)
if res.status == 429:
sleep(float(res.headers.get("Retry-After", delay)))
continue
if res.status >= 500:
sleep(delay + random() * delay / 2)
delay = min(delay * 2, 30)
continue
return validate(res.json())
return None # this cycle failed; the next one runs
Valida el 200
Un estado de éxito es el principio de la comprobación, no el final. La respuesta de mercados es un objeto con un array events y un cursor last; exige ambos antes de tocar el payload. Un array vacío en una hora tranquila es una respuesta válida y no debe registrarse como error, mientras que un cuerpo sin el cursor está malformado y no debe sustituir el snapshot que ya tienes. Nunca rellenes un precio ausente con cero: una money line nula significa que el mercado está cerrado o no disponible, y un cero parecería un precio para todo lo que viene después.
Qué debería avisar a una persona
Cuatro condiciones justifican despertar a alguien; el resto pertenece a un dashboard. Un 401 o 403 en cualquier momento, porque el poller no puede recuperarse solo. Más de un 429 por cada cien solicitudes durante una hora, porque la programación está demasiado cerca del techo. Una racha de 5xx o timeouts de más de cinco minutos, porque un fallo transitorio ha dejado de serlo. Y un snapshot más antiguo que tres intervalos de sondeo en cualquier tablero durante una cartelera, que es el síntoma que causan los otros tres. La página de monitorización convierte eso en métricas con umbrales.
Preguntas del turno de guardia
¿Un 429 debería reintentarse de inmediato alguna vez?
No. Incluso sin cabecera Retry-After, espera al menos el retraso base. Un reintento inmediato cae dentro de la misma ventana y alarga la limitación.
¿Un 403 es siempre un problema del plan?
En esta API casi siempre lo es: la clave fue aceptada y la solicitud fue rechazada. Compara el endpoint y el transporte que llamaste con los derechos del plan antes de cambiar nada en el código.
¿Cuántos intentos por ciclo son demasiados?
Más de tres. Cada intento extra gasta presupuesto en una llamada que probablemente vuelva a fallar, y el siguiente ciclo está a segundos de distancia. Falla rápido y deja que la programación se recupere.
Los significados de los estados y las asignaciones siguen la documentación publicada por el proveedor, revisada el 26 de septiembre de 2026. Publicado el .