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ó