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"
}
}
| Campo | Descripción |
|---|---|
code | Código de error legible para máquina para manejo programático |
message | Descripción de error legible para humanos |
details | Contexto adicional (opcional, varía por tipo de error) |
request_id | Identificador único de solicitud para consultas de soporte |
Códigos de Estado HTTP
| Código de Estado | Significado | Causas Comunes |
|---|---|---|
400 | Bad Request | Parámetros inválidos, JSON mal formado |
401 | Unauthorized | Clave de API faltante o inválida |
403 | Forbidden | Permisos insuficientes |
404 | Not Found | El recurso no existe |
413 | Payload Too Large | El archivo excede el límite de tamaño |
429 | Too Many Requests | Límite de frecuencia excedido |
500 | Internal Server Error | Error del lado del servidor |
503 | Service Unavailable | Interrupció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 requestsfrom 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
# Usotry: 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 timeimport requestsfrom 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)
# Usodef 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 timefrom 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
# Usobreaker = 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 loggingimport requests
# Habilitar logging de debuglogging.basicConfig(level=logging.DEBUG)
# Registrar solicitudes HTTPimport http.client as http_clienthttp_client.HTTPConnection.debuglevel = 1
# Tus llamadas a la API ahora mostrarán logs detalladosresponse = 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
- Siempre implementa lógica de reintento con backoff exponencial
- Respeta los headers Retry-After para límites de frecuencia
- Registra los IDs de solicitud para consultas de soporte
- Establece timeouts razonables (30-60 segundos para cargas de archivos)
- Usa circuit breakers para integraciones de alto volumen
- Monitorea tasas de error y configura alertas
- No reintentes errores 4xx (excepto 429) - no tendrán éxito
- Implementa degradación elegante - cachea resultados cuando sea posible
Próximos Pasos
¿Necesitas Ayuda?
- Email: support@callcov.com
- Incluye el
request_idde las respuestas de error - Documentación: docs.callcov.com