API ReferenceError Codes

Error Codes and Troubleshooting

This page documents all error codes returned by the Ledly API and how to resolve them.


Error Response Format

All API errors follow this standard format:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": []
  }
}

With Field-Level Details

Validation errors include field-specific information:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format",
        "value": "not-an-email"
      },
      {
        "field": "phone",
        "message": "Phone number must include country code",
        "value": "5551234567"
      }
    ]
  }
}

HTTP Status Codes

Status CodeMeaningWhen It Occurs
200OKRequest succeeded
201CreatedResource created successfully
204No ContentRequest succeeded with no response body
400Bad RequestInvalid request format or parameters
401UnauthorizedMissing or invalid authentication
403ForbiddenInsufficient permissions
404Not FoundResource doesn’t exist
409ConflictResource conflict (e.g., duplicate)
422Unprocessable EntityValid request but business logic error
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error
503Service UnavailableService temporarily unavailable

Authentication Errors (401)

INVALID_CREDENTIALS

Cause: Incorrect email or password during login.

Response:

{
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "Invalid email or password"
  }
}

Resolution:

  • Verify email and password are correct
  • Check for typos or extra whitespace
  • Use password reset if forgotten

TOKEN_EXPIRED

Cause: JWT token has expired (tokens last 24 hours).

Response:

{
  "error": {
    "code": "TOKEN_EXPIRED",
    "message": "Authentication token has expired",
    "expired_at": "2024-12-25T10:30:00Z"
  }
}

Resolution:

  • Login again to get a new token
  • Use the /api/auth/refresh endpoint to refresh before expiration

Example:

// Check token expiration before requests
if (isTokenExpired(token)) {
  const { token: newToken } = await refreshToken();
  // Retry request with new token
}

TOKEN_INVALID

Cause: JWT token is malformed or tampered with.

Response:

{
  "error": {
    "code": "TOKEN_INVALID",
    "message": "Authentication token is invalid"
  }
}

Resolution:

  • Ensure token is correctly formatted
  • Check for truncation or corruption
  • Login again to get a valid token

API_KEY_INVALID

Cause: API key not found or incorrectly formatted.

Response:

{
  "error": {
    "code": "API_KEY_INVALID",
    "message": "API key is invalid or has been revoked"
  }
}

Resolution:

  • Verify API key starts with vk_live_ (production) or vk_test_ (sandbox)
  • Check for typos or truncation
  • Ensure key hasn’t been revoked
  • Generate a new API key if needed

API_KEY_REVOKED

Cause: API key was manually revoked.

Response:

{
  "error": {
    "code": "API_KEY_REVOKED",
    "message": "This API key has been revoked",
    "revoked_at": "2024-12-20T15:00:00Z"
  }
}

Resolution:

  • Generate a new API key in Settings → API Keys
  • Update your integration with the new key

Authorization Errors (403)

INSUFFICIENT_PERMISSIONS

Cause: User or API key lacks required permissions.

Response:

{
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "You don't have permission to perform this action",
    "required_permission": "leads:write"
  }
}

Resolution:

  • Check user role has necessary permissions
  • Verify API key has required scopes
  • Contact organization admin to grant permissions

IP_RESTRICTED

Cause: Request from unauthorized IP address.

Response:

{
  "error": {
    "code": "IP_RESTRICTED",
    "message": "Access denied from this IP address",
    "ip_address": "203.0.113.42",
    "allowed_ips": ["198.51.100.0/24"]
  }
}

Resolution:

  • Add your IP to API key’s allowed IPs
  • Remove IP restrictions if not needed
  • Use VPN or proxy from allowed IP range

ORGANIZATION_SUSPENDED

Cause: Organization account is suspended.

Response:

{
  "error": {
    "code": "ORGANIZATION_SUSPENDED",
    "message": "Your organization account has been suspended",
    "reason": "Payment overdue",
    "suspended_at": "2024-12-20T00:00:00Z"
  }
}

Resolution:


Validation Errors (400)

VALIDATION_ERROR

Cause: Request data failed validation.

Response:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Invalid email format",
        "value": "not-an-email"
      },
      {
        "field": "phone",
        "message": "Phone number must be in E.164 format",
        "value": "123-456-7890"
      }
    ]
  }
}

Resolution:

  • Fix each field listed in details
  • Validate data client-side before submission
  • Refer to API documentation for field requirements

Common Validation Issues:

FieldIssueSolution
emailInvalid formatUse valid email: [email protected]
phoneMissing country codeInclude country code: +15551234567
passwordToo weakMin 8 chars, 1 uppercase, 1 number
custom_fieldsInvalid typeMatch expected type (string, number, boolean)

MISSING_REQUIRED_FIELD

Cause: Required field not provided.

Response:

{
  "error": {
    "code": "MISSING_REQUIRED_FIELD",
    "message": "Required field is missing",
    "field": "email"
  }
}

Resolution:

  • Include all required fields in request
  • Check API documentation for required fields

INVALID_EMAIL

Cause: Email address is invalid or from blocked domain.

Response:

{
  "error": {
    "code": "INVALID_EMAIL",
    "message": "Email address is invalid",
    "reason": "Domain has no MX records",
    "email": "[email protected]"
  }
}

Resolution:

  • Verify email is correctly spelled
  • Check domain exists and has valid MX records
  • Avoid disposable email providers (if blocked)

INVALID_PHONE_NUMBER

Cause: Phone number format is invalid.

Response:

{
  "error": {
    "code": "INVALID_PHONE_NUMBER",
    "message": "Phone number format is invalid",
    "phone": "123-456-7890",
    "expected_format": "E.164 (+15551234567)"
  }
}

Resolution:

  • Use E.164 format: +[country code][number]
  • Examples: +15551234567 (US), +442071234567 (UK)

Resource Errors (404)

LEAD_NOT_FOUND

Cause: Lead with specified ID doesn’t exist.

Response:

{
  "error": {
    "code": "LEAD_NOT_FOUND",
    "message": "Lead with ID 'lead_abc123' not found",
    "lead_id": "lead_abc123"
  }
}

Resolution:

  • Verify lead ID is correct
  • Check lead hasn’t been deleted
  • Ensure you have access to the lead’s organization

RESOURCE_NOT_FOUND

Cause: Generic resource not found.

Response:

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "The requested resource was not found",
    "resource_type": "vendor",
    "resource_id": "vendor_xyz789"
  }
}

Resolution:

  • Check resource ID is correct
  • Verify resource exists in your organization
  • Ensure proper authentication

Conflict Errors (409)

DUPLICATE_LEAD

Cause: Lead with same email already exists.

Response:

{
  "error": {
    "code": "DUPLICATE_LEAD",
    "message": "A lead with this email already exists",
    "duplicate_of": "lead_existing123",
    "matched_fields": ["email"],
    "similarity_score": 100
  }
}

Resolution:

  • Update existing lead instead of creating new one
  • Configure deduplication rules to allow duplicates
  • Use different email address

EMAIL_ALREADY_REGISTERED

Cause: Email already used for another account.

Response:

{
  "error": {
    "code": "EMAIL_ALREADY_REGISTERED",
    "message": "An account with this email already exists",
    "email": "[email protected]"
  }
}

Resolution:

  • Use different email address
  • Login to existing account
  • Use password reset if you forgot password

Business Logic Errors (422)

LEAD_REJECTED

Cause: Lead rejected by enrichment rules.

Response:

{
  "error": {
    "code": "LEAD_REJECTED",
    "message": "Lead rejected by validation rules",
    "reason": "Disposable email address not allowed",
    "rule_id": "rule_reject_disposable",
    "rule_name": "Reject Disposable Emails"
  }
}

Resolution:

  • Fix the issue identified in reason
  • Review enrichment rules if rejection is unexpected
  • Contact admin to modify rules if needed

WEAK_PASSWORD

Cause: Password doesn’t meet security requirements.

Response:

{
  "error": {
    "code": "WEAK_PASSWORD",
    "message": "Password does not meet security requirements",
    "requirements": {
      "min_length": 8,
      "require_uppercase": true,
      "require_lowercase": true,
      "require_number": true,
      "require_special": false
    }
  }
}

Resolution:

  • Use password with minimum 8 characters
  • Include at least one uppercase letter
  • Include at least one lowercase letter
  • Include at least one number

INVALID_RESET_TOKEN

Cause: Password reset token is invalid or expired.

Response:

{
  "error": {
    "code": "INVALID_RESET_TOKEN",
    "message": "Password reset token is invalid or has expired",
    "token_age_hours": 25,
    "max_age_hours": 24
  }
}

Resolution:

  • Request a new password reset email
  • Use reset link within 24 hours
  • Check for typos in reset token

Rate Limit Errors (429)

RATE_LIMIT_EXCEEDED

Cause: Too many requests in time window.

Response:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded",
    "limit": 100,
    "window": "1 minute",
    "retry_after": 45
  }
}

Response Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1703505660
Retry-After: 45

Resolution:

  • Wait for retry_after seconds before retrying
  • Implement exponential backoff
  • Reduce request frequency
  • Contact support for higher limits

Example:

async function makeRequest(url) {
  try {
    const response = await fetch(url);
 
    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After') || 60;
      await sleep(retryAfter * 1000);
      return makeRequest(url); // Retry
    }
 
    return response;
  } catch (error) {
    console.error('Request failed:', error);
  }
}

Server Errors (500)

INTERNAL_SERVER_ERROR

Cause: Unexpected server error.

Response:

{
  "error": {
    "code": "INTERNAL_SERVER_ERROR",
    "message": "An unexpected error occurred",
    "request_id": "req_abc123xyz",
    "timestamp": "2024-12-25T10:30:00Z"
  }
}

Resolution:

  • Retry the request with exponential backoff
  • If persists, contact support with request_id
  • Check status page for ongoing incidents

SERVICE_UNAVAILABLE

Cause: Service temporarily unavailable (maintenance, overload).

Response:

{
  "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "Service temporarily unavailable",
    "retry_after": 300
  }
}

Response Headers:

Retry-After: 300

Resolution:

  • Wait for retry_after seconds
  • Implement retry logic with backoff
  • Check status page for maintenance windows

Integration-Specific Errors

CRM_SYNC_FAILED

Cause: Failed to sync lead to CRM.

Response:

{
  "error": {
    "code": "CRM_SYNC_FAILED",
    "message": "Failed to sync lead to CRM",
    "crm_type": "salesforce",
    "crm_error": "INVALID_FIELD: Field 'Custom_Field__c' does not exist",
    "lead_id": "lead_abc123"
  }
}

Resolution:

  • Check CRM field mappings
  • Verify CRM credentials are valid
  • Review CRM error message for details
  • Update field mappings in Settings → CRM

WEBHOOK_DELIVERY_FAILED

Cause: Failed to deliver webhook to your endpoint.

Response:

{
  "error": {
    "code": "WEBHOOK_DELIVERY_FAILED",
    "message": "Failed to deliver webhook",
    "webhook_id": "webhook_abc123",
    "endpoint_url": "https://your-app.com/webhooks",
    "response_status": 500,
    "response_body": "Internal Server Error",
    "attempts": 3,
    "next_retry": "2024-12-25T12:00:00Z"
  }
}

Resolution:

  • Check webhook endpoint is accessible
  • Verify endpoint returns 2xx status
  • Review webhook logs in Ledly dashboard
  • Test webhook endpoint manually

EXPORT_FAILED

Cause: Failed to generate export.

Response:

{
  "error": {
    "code": "EXPORT_FAILED",
    "message": "Failed to generate export",
    "export_id": "export_abc123",
    "reason": "Query timeout",
    "row_count_attempted": 500000
  }
}

Resolution:

  • Reduce date range or filters
  • Try exporting in smaller batches
  • Contact support for large exports

Best Practices

1. Handle All Error Cases

async function makeApiRequest(url, options) {
  try {
    const response = await fetch(url, options);
 
    // Success
    if (response.ok) {
      return await response.json();
    }
 
    // Parse error
    const error = await response.json();
 
    // Handle specific errors
    switch (error.error.code) {
      case 'TOKEN_EXPIRED':
        await refreshToken();
        return makeApiRequest(url, options); // Retry
 
      case 'RATE_LIMIT_EXCEEDED':
        await sleep(error.error.retry_after * 1000);
        return makeApiRequest(url, options); // Retry
 
      case 'VALIDATION_ERROR':
        console.error('Validation failed:', error.error.details);
        throw error;
 
      default:
        throw error;
    }
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

2. Implement Retry Logic

async function retryWithBackoff(fn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      // Don't retry client errors (4xx)
      if (error.status >= 400 && error.status < 500) {
        throw error;
      }
 
      // Last attempt - throw error
      if (attempt === maxRetries - 1) {
        throw error;
      }
 
      // Wait with exponential backoff
      const delay = Math.pow(2, attempt) * 1000;
      await sleep(delay);
    }
  }
}

3. Log Request IDs

async function makeRequest(url, options) {
  try {
    const response = await fetch(url, options);
    const requestId = response.headers.get('X-Request-ID');
 
    if (!response.ok) {
      const error = await response.json();
      console.error('Request failed:', {
        requestId,
        errorCode: error.error.code,
        message: error.error.message
      });
    }
 
    return response;
  } catch (error) {
    console.error('Request failed:', error);
  }
}

4. Validate Before Sending

function validateLead(lead) {
  const errors = [];
 
  // Email validation
  if (!lead.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(lead.email)) {
    errors.push({ field: 'email', message: 'Invalid email format' });
  }
 
  // Phone validation
  if (lead.phone && !/^\+[1-9]\d{1,14}$/.test(lead.phone)) {
    errors.push({ field: 'phone', message: 'Invalid phone format (use E.164)' });
  }
 
  if (errors.length > 0) {
    throw new ValidationError(errors);
  }
}

5. Monitor Error Rates

const errorStats = {
  total: 0,
  byCode: {},
  byStatus: {}
};
 
function trackError(error) {
  errorStats.total++;
  errorStats.byCode[error.code] = (errorStats.byCode[error.code] || 0) + 1;
  errorStats.byStatus[error.status] = (errorStats.byStatus[error.status] || 0) + 1;
 
  // Alert if error rate is high
  if (errorStats.total > 100 && errorStats.byStatus[500] / errorStats.total > 0.05) {
    alertDevTeam('High server error rate');
  }
}

Getting Help

If you encounter an error you can’t resolve:

  1. Check this page for error code documentation
  2. Review API logs in your Ledly dashboard
  3. Check status page at status.ledly.io
  4. Contact support at [email protected] with:
    • Request ID (from X-Request-ID header)
    • Error code and message
    • Steps to reproduce
    • Timestamp of occurrence