Rate Limiting

Understand LendFoundry API rate limits, response headers, and best practices for handling rate-limited requests

Rate Limiting

📘

Fair Usage & System Stability

LendFoundry enforces rate limits on all API endpoints to ensure fair usage, system stability, and optimal performance for all customers.

Rate limiting protects the platform from abuse and ensures consistent performance for all API consumers.

⚠️

Tenant-Specific Limits

Rate limits may vary based on your subscription tier and specific tenant configuration. The values shown here are typical defaults. Contact your LendFoundry representative for your specific rate limits.


Rate Limit Headers

All API responses include rate limit information in the response headers:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in current window100
X-RateLimit-RemainingRequests remaining in current window95
X-RateLimit-ResetUnix timestamp when window resets1702648800
Retry-AfterSeconds to wait before retrying (429 only)60

Example Response Headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1702648800
Content-Type: application/json

Default Rate Limits

Application Submission APIs (LOS)

Limit TypeDefault ValueWindowNotes
Standard100 requestsPer minutePer API key
Burst20 requestsPer secondShort bursts allowed
Daily50,000 requestsPer daySoft limit*

Base URL: https://loc.demo.kendra.lendfoundry.com/v1/darbaan

Active Loan Servicing APIs (LMS)

Limit TypeDefault ValueWindowNotes
Standard200 requestsPer minutePer client_id
Burst30 requestsPer secondShort bursts allowed
Daily100,000 requestsPer daySoft limit*

Base URL: https://api.demo.lms.lendfoundry.com/v1

💡

Soft Limits

*Daily limits are soft limits. You may receive warnings but requests won't be immediately blocked. Contact support if you consistently exceed daily limits.


Rate Limits by Endpoint Category

Some endpoints have stricter rate limits due to resource intensity:

High-Volume Endpoints (Stricter Limits)

Endpoint CategoryDefault LimitWindowReason
Search/Filter50 requestsPer minuteDatabase-intensive
Bulk Operations20 requestsPer minuteHigh processing load
Report Generation10 requestsPer minuteCPU-intensive
Credit Bureau Pulls30 requestsPer hourThird-party costs

Standard Endpoints

Endpoint CategoryDefault LimitWindow
CRUD Operations100 requestsPer minute
Read Operations200 requestsPer minute
Webhook Config10 requestsPer minute

Rate Limit Exceeded Response

When you exceed rate limits, you'll receive a 429 Too Many Requests response:

Response Body

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please retry after the specified time.",
    "details": {
      "limit": 100,
      "remaining": 0,
      "reset_at": "2025-12-15T10:31:00Z",
      "retry_after": 60
    },
    "trace_id": "a1b2c3d4e5"
  }
}

Response Headers

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1702648860
Retry-After: 60
Content-Type: application/json

Handling Rate Limits

Strategy 1: Respect Retry-After Header

Always check the Retry-After header before retrying:

# Check response headers and wait if rate limited
curl -I "https://api.demo.lms.lendfoundry.com/v1/loan-management/search/LN-2024-001/details" \
  -H "Authorization: Bearer YOUR_TOKEN"
const makeRequestWithRetry = async (url, options, maxRetries = 3) => {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);
    
    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
      console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
      await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      continue;
    }
    
    return response;
  }
  
  throw new Error('Max retries exceeded');
};
import time
import requests

def make_request_with_retry(url, headers, max_retries=3):
    """Make request with automatic retry on rate limit."""
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
        
        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Rate limited. Waiting {retry_after} seconds...")
            time.sleep(retry_after)
            continue
        
        return response
    
    raise Exception("Max retries exceeded")
public HttpResponse<String> makeRequestWithRetry(String url, Map<String, String> headers, int maxRetries) throws Exception {
    HttpClient client = HttpClient.newHttpClient();
    
    for (int attempt = 0; attempt < maxRetries; attempt++) {
        HttpRequest.Builder builder = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .GET();
        
        headers.forEach(builder::header);
        HttpResponse<String> response = client.send(builder.build(), 
            HttpResponse.BodyHandlers.ofString());
        
        if (response.statusCode() == 429) {
            String retryAfter = response.headers().firstValue("Retry-After").orElse("60");
            System.out.println("Rate limited. Waiting " + retryAfter + " seconds...");
            Thread.sleep(Integer.parseInt(retryAfter) * 1000);
            continue;
        }
        
        return response;
    }
    
    throw new Exception("Max retries exceeded");
}

Strategy 2: Exponential Backoff

Implement exponential backoff for all retryable errors:

import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_retry_session(retries=3, backoff_factor=1):
    """Create requests session with automatic retry."""
    session = requests.Session()
    
    retry = Retry(
        total=retries,
        read=retries,
        connect=retries,
        backoff_factor=backoff_factor,
        status_forcelist=(429, 500, 502, 503, 504),
        respect_retry_after_header=True
    )
    
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    
    return session

# Usage
session = create_retry_session()
response = session.get(url, headers=headers)

Strategy 3: Request Batching

Reduce request count by batching operations:

# ❌ Bad - 10 separate requests
for loan_id in loan_ids:
    response = requests.get(f"/loans/{loan_id}", headers=headers)

# ✅ Good - 1 batch request (if available)
response = requests.post("/loans/batch", 
    json={"loan_ids": loan_ids}, 
    headers=headers
)

Strategy 4: Caching

Cache responses to avoid redundant requests:

from functools import lru_cache
import time

class CachedAPIClient:
    """API client with response caching."""
    
    def __init__(self, base_url, headers, cache_ttl=300):
        self.base_url = base_url
        self.headers = headers
        self.cache = {}
        self.cache_ttl = cache_ttl
    
    def get_loan(self, loan_number):
        """Get loan with caching."""
        cache_key = f"loan_{loan_number}"
        
        # Check cache
        if cache_key in self.cache:
            data, cached_time = self.cache[cache_key]
            if time.time() - cached_time < self.cache_ttl:
                return data
        
        # Fetch from API
        response = requests.get(
            f"{self.base_url}/loan-management/search/{loan_number}/details",
            headers=self.headers
        )
        response.raise_for_status()
        data = response.json()
        
        # Update cache
        self.cache[cache_key] = (data, time.time())
        
        return data

Rate Limit Monitoring

Monitor Current Usage

def check_rate_limit_status(response):
    """Extract rate limit info from response headers."""
    from datetime import datetime
    
    limit = int(response.headers.get('X-RateLimit-Limit', 0))
    remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
    reset_timestamp = int(response.headers.get('X-RateLimit-Reset', 0))
    
    if limit > 0:
        usage_percent = ((limit - remaining) / limit) * 100
        reset_at = datetime.fromtimestamp(reset_timestamp)
        
        return {
            "limit": limit,
            "remaining": remaining,
            "reset_at": reset_at,
            "usage_percent": usage_percent
        }
    
    return None

# Usage
response = requests.get(url, headers=headers)
status = check_rate_limit_status(response)

if status:
    print(f"Rate limit: {status['usage_percent']:.1f}% used")
    print(f"Remaining: {status['remaining']} requests")
    print(f"Resets at: {status['reset_at']}")

Alert on High Usage

class RateLimitMonitor:
    """Monitor rate limit usage and alert when high."""
    
    def __init__(self, warning_threshold=0.8):
        self.warning_threshold = warning_threshold
    
    def check_response(self, response):
        """Check response and alert if approaching limit."""
        status = check_rate_limit_status(response)
        
        if status and status['usage_percent'] >= self.warning_threshold * 100:
            self._send_alert(status)
        
        return status
    
    def _send_alert(self, status):
        """Send alert when approaching rate limit."""
        print(f"⚠️  Rate limit warning: {status['usage_percent']:.1f}% used")
        print(f"   Only {status['remaining']} requests remaining")
        # Add your alerting logic here (Slack, email, etc.)

# Usage
monitor = RateLimitMonitor(warning_threshold=0.8)
response = requests.get(url, headers=headers)
monitor.check_response(response)

Best Practices

✅ DO

PracticeImplementation
Monitor rate limit headersCheck X-RateLimit-Remaining after each request
Implement exponential backoffStart with 1s, double on each retry
Respect Retry-After headerWait specified seconds before retry
Batch requests when possibleUse bulk endpoints to reduce request count
Cache responsesSet appropriate TTL for GET requests
Use webhooksSubscribe to events instead of polling

❌ DON'T

Anti-PatternRisk
Ignore rate limitsBlocked requests, degraded service
Retry immediatelyWastes quota, extends block time
Poll for statusExhausts rate limits quickly
Hardcode delaysDoesn't adapt to actual limits

Requesting Higher Limits

When to Request

  • Consistently hitting rate limits with legitimate traffic
  • High-volume production use case
  • Enterprise tier customer

How to Request

  1. Contact Support

  2. Provide Information

    • Current usage patterns
    • Expected request volume
    • Use case description
    • Business justification
  3. Review Process

    • Support reviews request
    • May require tier upgrade
    • Limits adjusted within 2-3 business days

FAQ

Q: Do rate limits reset at midnight?

A: No. Rate limits use sliding windows or fixed windows based on limit type. Check the X-RateLimit-Reset header for exact reset time.

Q: Are rate limits per API key or per organization?

A: Rate limits are per API key (LOS) or per client_id (LMS). Multiple credentials = multiple rate limit buckets.

Q: What happens if I exceed daily limits?

A: Daily limits are soft limits. You may receive warnings but requests won't be immediately blocked. Contact support if consistently exceeding.

Q: Do rate limits apply to webhooks?

A: No. Webhooks are outbound from LendFoundry to your server. Rate limits only apply to inbound API requests.

Q: Can I get higher rate limits?

A: Yes. Contact support with your use case. Higher limits may require enterprise tier subscription.


Support

Questions about rate limits?


Properly Handling Rate Limits?

Head to the Error Codes reference to understand all possible error responses.