Manejo de Errores
Esta guía cubre todos los códigos de error de la API y cómo manejarlos correctamente.
Formato de Errores
Todos los errores siguen este formato JSON:
{
"error": "error_code",
"message": "Descripción legible del error"
}
Algunos errores incluyen campos adicionales como retry_after o code para información más detallada.
Códigos HTTP
| Código | Significado | Acción Recomendada |
|---|---|---|
400 |
Bad Request | Corregir el request |
401 |
Unauthorized | Verificar API Key |
402 |
Payment Required | Comprar créditos |
403 |
Forbidden | Verificar plan/permisos |
404 |
Not Found | Verificar ID del recurso |
429 |
Too Many Requests | Esperar y reintentar |
500 |
Internal Server Error | Reintentar con backoff |
503 |
Service Unavailable | Reintentar más tarde |
Errores de Autenticación (401)
unauthorized
{
"error": "unauthorized",
"message": "API key is required. Provide it via X-API-Key header."
}
Causas:
- Header X-API-Key no incluido
- API Key inválida o mal formateada
- API Key revocada
Solución:
# Verificar que la key está incluida
headers = {"X-API-Key": "sk_live_tu_key_aqui"}
# Verificar formato correcto
assert api_key.startswith("sk_live_"), "Invalid key format"
invalid_api_key
{
"error": "invalid_api_key",
"message": "The API key provided is invalid."
}
Solución: Verifica que estás usando una API Key válida de tu cuenta.
api_key_revoked
{
"error": "api_key_revoked",
"message": "This API key has been revoked."
}
Solución: Crea una nueva API Key en Perfil → API → API Keys.
Errores de Autorización (403)
forbidden
{
"error": "forbidden",
"message": "API access not available for your plan"
}
Causa: Plan Basic o Scale intentando usar la API.
Solución: Contacta con nuestro equipo comercial para acceder a un plan con acceso a la API (Professional, Elite o Custom).
company_suspended
{
"error": "company_suspended",
"message": "Your company account has been suspended"
}
Solución: Contacta con nuestro equipo de soporte.
Errores de Validación (400)
validation_error
{
"error": "validation_error",
"message": "Invalid request data"
}
Para errores con detalles de campos específicos:
{
"error": "validation_error",
"message": "Validation failed",
"details": [
{
"field": "leads[0].linkedin_url",
"message": "Required field"
}
]
}
Solución:
def validate_lead(lead):
errors = []
if not lead.get('linkedin_url'):
errors.append("linkedin_url is required")
return errors
max_leads_exceeded
{
"error": "max_leads_exceeded",
"message": "Maximum 100 leads per request"
}
Solución: Dividir en múltiples jobs:
def create_jobs_batched(leads, batch_size=100):
jobs = []
for i in range(0, len(leads), batch_size):
batch = leads[i:i + batch_size]
job = api.create_job(batch)
jobs.append(job)
return jobs
duplicate_linkedin_url
{
"error": "duplicate_linkedin_url",
"message": "Duplicate linkedin_url found in request",
"details": [
{
"linkedin_url": "https://www.linkedin.com/in/johndoe/",
"positions": [2, 5]
}
]
}
Solución: Elimina URLs duplicadas antes de enviar.
Errores de Pago (402)
insufficient_credits
{
"error": "insufficient_credits",
"message": "Not enough credits. Available: 50, Required: 200"
}
Solución:
def ensure_credits_available(leads):
credits_needed = len(leads)
usage = api.get_usage()
if usage['credits_available'] < credits_needed:
shortfall = credits_needed - usage['credits_available']
raise InsufficientCreditsError(
f"Need {shortfall} more credits."
)
Errores de Recurso No Encontrado (404)
job_not_found
{
"error": "job_not_found",
"message": "Job not found"
}
Causas: - Job ID incorrecto - Job de otra empresa - Job expirado (>30 días)
job_not_completed
{
"error": "job_not_completed",
"message": "Results are only available for completed jobs"
}
Solución: Esperar a que status sea completed antes de pedir resultados.
api_key_not_found
{
"error": "api_key_not_found",
"message": "API key not found"
}
Solución: Verifica el key_id que estás usando.
Errores de Rate Limit (429)
rate_limit_exceeded
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Maximum 300 requests per minute.",
"retry_after": 32
}
Headers adicionales:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704710460
Retry-After: 32
Solución:
import time
def request_with_retry(request_func):
try:
return request_func()
except RateLimitError as e:
time.sleep(e.retry_after)
return request_func()
max_concurrent_jobs_exceeded
{
"error": "max_concurrent_jobs_exceeded",
"message": "Maximum 10 concurrent jobs allowed. Wait for existing jobs to complete."
}
Solución: Esperar a que terminen jobs existentes.
Errores de Servidor (500, 503)
internal_error
{
"error": "internal_error",
"message": "An internal error occurred. Please try again."
}
Solución:
def request_with_backoff(request_func, max_retries=3):
for attempt in range(max_retries):
try:
return request_func()
except InternalServerError as e:
if attempt == max_retries - 1:
raise
wait = 2 ** attempt # 1, 2, 4 segundos
time.sleep(wait)
service_unavailable
{
"error": "service_unavailable",
"message": "Service temporarily unavailable. Please try again later."
}
Causa: Mantenimiento o alta carga temporal.
Solución: Esperar unos minutos y reintentar.
Cliente con Manejo de Errores Completo
Python
import requests
import time
from typing import Any
class MasLeadsError(Exception):
def __init__(self, error: str, message: str, **kwargs):
self.error = error
self.message = message
self.extra = kwargs
super().__init__(f"{error}: {message}")
class AuthenticationError(MasLeadsError): pass
class PaymentError(MasLeadsError): pass
class ValidationError(MasLeadsError): pass
class RateLimitError(MasLeadsError):
def __init__(self, error, message, retry_after=60, **kwargs):
super().__init__(error, message, **kwargs)
self.retry_after = retry_after
class NotFoundError(MasLeadsError): pass
class ServerError(MasLeadsError): pass
class MasLeadsClient:
BASE_URL = "https://api.masleads.com/api/v1"
def __init__(self, api_key: str):
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({
"X-API-Key": api_key,
"Content-Type": "application/json"
})
def _handle_error(self, response: requests.Response):
"""Convierte respuestas de error en excepciones."""
try:
data = response.json()
error = data.get("error", "unknown")
message = data.get("message", "Unknown error")
except:
error = "unknown"
message = response.text
if response.status_code == 401:
raise AuthenticationError(error, message)
elif response.status_code == 402:
raise PaymentError(error, message)
elif response.status_code == 403:
raise AuthenticationError(error, message)
elif response.status_code == 400:
raise ValidationError(error, message)
elif response.status_code == 404:
raise NotFoundError(error, message)
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise RateLimitError(error, message, retry_after)
elif response.status_code >= 500:
raise ServerError(error, message)
def _request(self, method: str, endpoint: str, **kwargs) -> Any:
"""Hace request con manejo de errores."""
url = f"{self.BASE_URL}{endpoint}"
response = self.session.request(method, url, **kwargs)
if not response.ok:
self._handle_error(response)
return response.json()
def _request_with_retry(self, method: str, endpoint: str,
max_retries: int = 3, **kwargs) -> Any:
"""Request con retry automático."""
for attempt in range(max_retries):
try:
return self._request(method, endpoint, **kwargs)
except RateLimitError as e:
if attempt == max_retries - 1:
raise
time.sleep(e.retry_after)
except ServerError:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
def create_job(self, leads: list, field: str):
return self._request_with_retry(
"POST", "/enrich/leads",
json={"leads": leads, "field": field}
)
def get_job_status(self, job_id: str):
return self._request_with_retry("GET", f"/jobs/{job_id}")
def get_job_results(self, job_id: str):
return self._request_with_retry("GET", f"/jobs/{job_id}/results")
def get_usage(self):
return self._request_with_retry("GET", "/usage")
# Uso
client = MasLeadsClient("sk_live_tu_key")
try:
job = client.create_job(
leads=[{"linkedin_url": "https://linkedin.com/in/johndoe"}],
field="email"
)
except AuthenticationError:
print("API Key inválida - verificar configuración")
except PaymentError as e:
print(f"Sin créditos: {e.message}")
except ValidationError as e:
print(f"Datos inválidos: {e.message}")
except RateLimitError as e:
print(f"Rate limit - esperar {e.retry_after}s")
Mejores Prácticas
1. Siempre Manejar Errores
# ❌ Malo
response = api.create_job(leads)
# ✅ Bueno
try:
response = api.create_job(leads)
except MasLeadsError as e:
log_error(e)
handle_gracefully(e)
2. Logging con Contexto
import logging
logger = logging.getLogger(__name__)
try:
job = api.create_job(leads)
except MasLeadsError as e:
logger.error(
"API error",
extra={
"error": e.error,
"message": e.message,
"leads_count": len(leads)
}
)
3. Alertas en Errores Críticos
def handle_error(error):
if isinstance(error, PaymentError):
alert_admin("Credits depleted - action required")
elif isinstance(error, AuthenticationError) and error.error == "api_key_revoked":
alert_admin("API Key revoked - check settings")
4. Reintentos Inteligentes
| Error | ¿Reintentar? | Estrategia |
|---|---|---|
| 400 Validation | ❌ No | Corregir datos |
| 401 Auth | ❌ No | Verificar key |
| 402 Payment | ❌ No | Comprar créditos |
| 429 Rate Limit | ✅ Sí | Esperar retry_after |
| 500 Server | ✅ Sí | Backoff exponencial |
| 503 Unavailable | ✅ Sí | Esperar y reintentar |
Soporte
Si encuentras errores no documentados o necesitas ayuda, incluye siempre: - El código de error exacto - Un ejemplo del request que falló