LendWorksLendWorksDocs

Error Handling

Understand error codes, status codes, and how to handle API errors gracefully.

HTTP Status Codes

StatusMeaningWhen
200OKSuccessful read or update
201CreatedResource successfully created
400Bad RequestMalformed JSON or invalid request structure
401UnauthorizedMissing, invalid, or revoked API key
403ForbiddenKey lacks permission for this operation
404Not FoundResource doesn't exist or is outside your organization
409ConflictIdempotency key conflict or duplicate resource
422Unprocessable EntityRequest body failed validation
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error

Error Response Format

All errors use a consistent envelope with one or more error objects:

{
  "errors": [
    {
      "code": "VALIDATION_ERROR",
      "message": "Field 'email' must be a valid email address",
      "detail": { "field": "email" }
    }
  ],
  "meta": {
    "requestId": "550e8400-e29b-41d4-a716-446655440000"
  }
}
FieldTypeDescription
codestringMachine-readable error code
messagestringHuman-readable description
detailobjectAdditional context (field names, limits, etc.)

Error Codes

Authentication Errors (401)

CodeDescription
API_KEY_REQUIREDNo Authorization header provided
INVALID_API_KEYKey not found or secret doesn't match
API_KEY_REVOKEDKey has been revoked
API_KEY_EXPIREDKey has expired

Permission Errors (403)

CodeDescription
FORBIDDENAPI key permission level doesn't allow this method

Validation Errors (422)

{
  "errors": [
    {
      "code": "VALIDATION_ERROR",
      "message": "Field 'email' must be a valid email address",
      "detail": { "field": "email" }
    },
    {
      "code": "VALIDATION_ERROR",
      "message": "Field 'businessName' must be between 1 and 255 characters",
      "detail": { "field": "businessName" }
    }
  ]
}

Multiple validation errors can be returned in a single response. Fix all reported issues before retrying.

Rate Limit Errors (429)

{
  "errors": [
    {
      "code": "RATE_LIMIT_EXCEEDED",
      "message": "Rate limit exceeded. Try again in 45 seconds.",
      "detail": {
        "retryAfter": 45,
        "limit": 1000,
        "remaining": 0,
        "resetAt": "2025-01-15T10:30:00Z"
      }
    }
  ]
}

The Retry-After header is also set on the response. See Rate Limits for details.

Quota Errors (429)

{
  "errors": [
    {
      "code": "MONTHLY_QUOTA_EXCEEDED",
      "message": "Monthly API quota exceeded. Upgrade your plan for higher limits.",
      "detail": {
        "currentPlan": "basic",
        "resetAt": "2025-02-01T00:00:00Z"
      }
    }
  ]
}

Idempotency Errors

CodeStatusDescription
INVALID_IDEMPOTENCY_KEY422Key format is invalid (must be 1-256 alphanumeric/hyphens/underscores)
IDEMPOTENCY_CONFLICT409Another request with this key is currently in-flight

Not Found (404)

{
  "errors": [
    {
      "code": "NOT_FOUND",
      "message": "Lead not found"
    }
  ]
}

Resources outside your organization also return 404 (not 403) to prevent information leakage.

Handling Errors in the SDK

The SDK throws typed errors that you can catch:

import { LendWorksClient, LendWorksError } from '@lendworks/sdk-node'

const client = new LendWorksClient({ apiKey: 'lw_live_xxxx' })

try {
  const lead = await client.leads.create({
    businessName: 'Acme Corp',
    email: 'not-an-email',
  })
} catch (error) {
  if (error instanceof LendWorksError) {
    console.error(`API Error: ${error.code} — ${error.message}`)
    console.error(`Request ID: ${error.requestId}`)
    console.error(`Status: ${error.status}`)

    if (error.code === 'RATE_LIMIT_EXCEEDED') {
      // Wait and retry
      await new Promise(r => setTimeout(r, error.detail.retryAfter * 1000))
    }
  }
}

Retry Strategy

StatusRetry?Notes
400NoFix your request
401NoCheck your API key
403NoCheck your key's permission level
404NoResource doesn't exist
409NoResolve the conflict
422NoFix validation errors
429YesWait for Retry-After seconds
500YesUse exponential backoff with jitter

For 500 errors, use exponential backoff:

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn()
    } catch (error) {
      if (error.status !== 500 || attempt === maxRetries) throw error
      const delay = Math.min(1000 * 2 ** attempt + Math.random() * 1000, 30000)
      await new Promise(r => setTimeout(r, delay))
    }
  }
  throw new Error('unreachable')
}

Always include an Idempotency-Key when retrying write operations to prevent duplicate creates.