Saltar al contenido principal

Manejo de Errores

Aprende cómo manejar errores elegantemente al integrar con la API de CallCov. Esta guía cubre escenarios de error comunes, estrategias de reintento y técnicas de depuración.

Formato de Respuesta de Error

Todos los errores de la API siguen un formato JSON consistente:

{
"error": {
"code": "código_de_error_aquí",
"message": "Mensaje de error legible para humanos",
"details": {
"field": "contexto_adicional"
},
"request_id": "req_1a2b3c4d5e"
}
}
CampoDescripción
codeCódigo de error legible para máquina para manejo programático
messageDescripción de error legible para humanos
detailsContexto adicional (opcional, varía por tipo de error)
request_idIdentificador único de solicitud para consultas de soporte

Códigos de Estado HTTP

Código de EstadoSignificadoCausas Comunes
400Bad RequestParámetros inválidos, JSON mal formado
401UnauthorizedClave de API faltante o inválida
403ForbiddenPermisos insuficientes
404Not FoundEl recurso no existe
413Payload Too LargeEl archivo excede el límite de tamaño
429Too Many RequestsLímite de frecuencia excedido
500Internal Server ErrorError del lado del servidor
503Service UnavailableInterrupción temporal del servicio

Errores Comunes y Soluciones

401 No Autorizado

Respuesta de Error:

{
"error": {
"code": "unauthorized",
"message": "Clave de API inválida o ausente"
}
}

Soluciones:

  • Verifica que la clave de API sea correcta y esté activa
  • Verifica que el nombre del header sea exactamente X-API-Key
  • Asegúrate de que no haya espacios en blanco extra en el valor de la clave
  • Regenera la clave si potencialmente está comprometida

400 Bad Request

Escenarios Comunes:

Formato de Archivo Inválido:

{
"error": {
"code": "invalid_file_format",
"message": "El archivo de audio debe estar en formato WAV, MP3, M4A, FLAC u OGG",
"details": {
"provided_format": "pdf"
}
}
}

Campo Requerido Faltante:

{
"error": {
"code": "validation_error",
"message": "Faltan campos requeridos",
"details": {
"missing_fields": ["agent_id", "contact_id"]
}
}
}

413 Payload Too Large

{
"error": {
"code": "file_too_large",
"message": "El archivo de audio excede el tamaño máximo de 100 MB",
"details": {
"file_size_mb": 150,
"max_size_mb": 100
}
}
}

Soluciones:

  • Comprime el archivo de audio (reduce la tasa de bits manteniendo la calidad)
  • Divide grabaciones largas en segmentos
  • Usa envío por URL para archivos muy grandes

429 Límite de Frecuencia Excedido

{
"error": {
"code": "rate_limit_exceeded",
"message": "Límite de frecuencia excedido. Intenta nuevamente en 32 segundos.",
"details": {
"retry_after": 32,
"limit": "60/minuto"
}
}
}

Headers de Respuesta:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1699564832
Retry-After: 32

Implementar Manejo de Errores

Manejo Básico de Errores

import requests
from requests.exceptions import RequestException
def analyze_call(audio_file_path, agent_id, contact_id, api_key):
"""Enviar llamada con manejo adecuado de errores"""
try:
files = {"audio_file": open(audio_file_path, "rb")}
data = {"agent_id": agent_id, "contact_id": contact_id}
headers = {"X-API-Key": api_key}
response = requests.post(
"https://api.callcov.com/api/v1/calls/analyze",
headers=headers,
files=files,
data=data,
timeout=30
)
# Lanzar excepción para códigos de estado 4xx/5xx
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
error_data = e.response.json().get("error", {})
if status_code == 401:
print(f"Autenticación falló: {error_data.get('message')}")
elif status_code == 400:
print(f"Solicitud inválida: {error_data.get('message')}")
print(f"Detalles: {error_data.get('details')}")
elif status_code == 429:
retry_after = e.response.headers.get('Retry-After', 60)
print(f"Límite de frecuencia. Reintentar después de {retry_after} segundos")
elif status_code >= 500:
print(f"Error del servidor: {error_data.get('message')}")
else:
print(f"Error HTTP {status_code}: {error_data.get('message')}")
raise
except requests.exceptions.Timeout:
print("La solicitud agotó el tiempo después de 30 segundos")
raise
except RequestException as e:
print(f"Error de red: {e}")
raise
# Uso
try:
result = analyze_call("call.wav", "AGENT_001", "CONTACT_001", "your_api_key")
print(f"Éxito: {result['analysis_id']}")
except Exception as e:
print(f"Falló al analizar la llamada: {e}")

Estrategias de Reintento

Backoff Exponencial

Implementa backoff exponencial para errores transitorios (límites de frecuencia, errores de servidor):

import time
import requests
from typing import Optional
def exponential_backoff_retry(
func,
max_retries=5,
initial_delay=1,
max_delay=60,
backoff_factor=2
):
"""Ejecutar función con lógica de reintento con backoff exponencial"""
for attempt in range(max_retries):
try:
return func()
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
# No reintentar errores de cliente (excepto límites de frecuencia)
if 400 <= status_code < 500 and status_code != 429:
raise
if attempt == max_retries - 1:
raise # Último intento falló
# Calcular delay con backoff exponencial
delay = min(initial_delay * (backoff_factor ** attempt), max_delay)
# Usar header Retry-After si está disponible
if status_code == 429:
retry_after = e.response.headers.get('Retry-After')
if retry_after:
delay = int(retry_after)
print(f"Intento {attempt + 1} falló. Reintentando en {delay}s...")
time.sleep(delay)
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
delay = min(initial_delay * (backoff_factor ** attempt), max_delay)
print(f"Error de red. Reintentando en {delay}s...")
time.sleep(delay)
# Uso
def submit_call():
return requests.post(
"https://api.callcov.com/api/v1/calls/analyze",
headers={"X-API-Key": "your_api_key"},
files={"audio_file": open("call.wav", "rb")},
data={"agent_id": "AGENT_001", "contact_id": "CONTACT_001"}
)
try:
response = exponential_backoff_retry(submit_call)
print(f"Éxito: {response.json()['analysis_id']}")
except Exception as e:
print(f"Todos los reintentos fallaron: {e}")

Patrón Circuit Breaker

Previene fallos en cascada deteniendo temporalmente las solicitudes después de fallos repetidos:

import time
from enum import Enum
class CircuitState(Enum):
CLOSED = "closed" # Operación normal
OPEN = "open" # Bloqueando solicitudes
HALF_OPEN = "half_open" # Probando si el servicio se recuperó
class CircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.failures = 0
self.last_failure_time = None
self.state = CircuitState.CLOSED
def call(self, func):
"""Ejecutar función con protección de circuit breaker"""
if self.state == CircuitState.OPEN:
if time.time() - self.last_failure_time > self.timeout:
# Intentar transición a half-open
self.state = CircuitState.HALF_OPEN
else:
raise Exception("Circuit breaker está ABIERTO. Servicio no disponible.")
try:
result = func()
# Éxito - reiniciar fallos
if self.state == CircuitState.HALF_OPEN:
self.state = CircuitState.CLOSED
self.failures = 0
return result
except Exception as e:
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.failure_threshold:
self.state = CircuitState.OPEN
print(f"Circuit breaker abierto después de {self.failures} fallos")
raise
# Uso
breaker = CircuitBreaker(failure_threshold=3, timeout=30)
def submit_call():
return requests.post(
"https://api.callcov.com/api/v1/calls/analyze",
headers={"X-API-Key": "your_api_key"},
files={"audio_file": open("call.wav", "rb")},
data={"agent_id": "AGENT_001", "contact_id": "CONTACT_001"}
)
try:
response = breaker.call(submit_call)
print(f"Éxito: {response.json()['analysis_id']}")
except Exception as e:
print(f"Solicitud falló: {e}")

Consejos de Depuración

Habilitar Logging de Solicitudes

import logging
import requests
# Habilitar logging de debug
logging.basicConfig(level=logging.DEBUG)
# Registrar solicitudes HTTP
import http.client as http_client
http_client.HTTPConnection.debuglevel = 1
# Tus llamadas a la API ahora mostrarán logs detallados
response = requests.post(...)

Guardar IDs de Solicitud

Siempre registra el request_id de las respuestas de error - ayuda a soporte a diagnosticar problemas:

try:
response = requests.post(...)
except requests.exceptions.HTTPError as e:
error_data = e.response.json().get("error", {})
request_id = error_data.get("request_id")
print(f"Solicitud falló. Referencia de soporte: {request_id}")

Mejores Prácticas

  1. Siempre implementa lógica de reintento con backoff exponencial
  2. Respeta los headers Retry-After para límites de frecuencia
  3. Registra los IDs de solicitud para consultas de soporte
  4. Establece timeouts razonables (30-60 segundos para cargas de archivos)
  5. Usa circuit breakers para integraciones de alto volumen
  6. Monitorea tasas de error y configura alertas
  7. No reintentes errores 4xx (excepto 429) - no tendrán éxito
  8. Implementa degradación elegante - cachea resultados cuando sea posible

Próximos Pasos

¿Necesitas Ayuda?