Learn how to authenticate with LendFoundry APIs using API Keys and Bearer JWT tokens
Authentication
Two Authentication MethodsApplication Submission APIs use API Key authentication. Active Loan Servicing APIs use Bearer JWT authentication. Using the wrong method is the most common integration error.
LendFoundry uses different authentication methods for each system to provide optimal security for different use cases.
Authentication Quick Reference
%%{init: {'theme': 'base', 'themeVariables': {'background': '#ffffff', 'mainBkg': '#ffffff', 'clusterBkg': '#f9fafb', 'clusterBorder': '#2563eb', 'primaryColor': '#2563eb', 'primaryTextColor': '#ffffff', 'titleColor': '#000000', 'lineColor': '#374151'}, 'flowchart': {'padding': 25, 'nodeSpacing': 45, 'rankSpacing': 60}}}%%
flowchart LR
subgraph AUTH[🔐 Authentication Methods]
direction TB
A[🖥️ Your App] --> B{Which System?}
B -->|LOS| C[🔑 API Key]
B -->|LMS| D[🎫 Bearer JWT]
end
style A fill:#2563eb,stroke:#1e40af,color:#fff
style B fill:#6b7280,stroke:#4b5563,color:#fff
style C fill:#059669,stroke:#047857,color:#fff
style D fill:#059669,stroke:#047857,color:#fff
| System | Header Format | Example |
|---|---|---|
| Application Submission APIs | Authorization: TOKEN | Authorization: abc123def456 |
| Active Loan Servicing APIs | Authorization: Bearer TOKEN | Authorization: Bearer eyJhbG... |
Critical Difference
- Application Submission APIs: No
Bearerprefix- Active Loan Servicing APIs: Requires
Bearerprefix
Application Submission APIs Authentication
Application Submission APIs use simple API Key authentication. The API key is passed directly in the Authorization header without any prefix.
Header Format
Authorization: your-api-key-here
Making Authenticated Requests
curl -X POST "https://loc.demo.kendra.lendfoundry.com/v1/darbaan/back-office/rest/api/list-applications" \
-H "Authorization: your-api-key-here" \
-H "Content-Type: application/json" \
-d '{"page": 1, "size": 10}'const listApplications = async (apiKey) => {
const response = await fetch(
'https://loc.demo.kendra.lendfoundry.com/v1/darbaan/back-office/rest/api/list-applications',
{
method: 'POST',
headers: {
'Authorization': apiKey, // No Bearer prefix
'Content-Type': 'application/json'
},
body: JSON.stringify({ page: 1, size: 10 })
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
};
// Usage
const apiKey = process.env.LENDFOUNDRY_LOS_API_KEY;
const applications = await listApplications(apiKey);import requests
import os
def list_applications(api_key):
"""List loan applications using LOS API."""
url = "https://loc.demo.kendra.lendfoundry.com/v1/darbaan/back-office/rest/api/list-applications"
headers = {
"Authorization": api_key, # No Bearer prefix
"Content-Type": "application/json"
}
response = requests.post(url, json={"page": 1, "size": 10}, headers=headers)
response.raise_for_status()
return response.json()
# Usage
api_key = os.getenv('LENDFOUNDRY_LOS_API_KEY')
applications = list_applications(api_key)public class LOSClient {
private final String apiKey;
private final HttpClient client;
public LOSClient(String apiKey) {
this.apiKey = apiKey;
this.client = HttpClient.newHttpClient();
}
public String listApplications() throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://loc.demo.kendra.lendfoundry.com/v1/darbaan/back-office/rest/api/list-applications"))
.header("Authorization", apiKey) // No Bearer prefix
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"page\": 1, \"size\": 10}"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
return response.body();
}
}
// Usage
String apiKey = System.getenv("LENDFOUNDRY_LOS_API_KEY");
LOSClient client = new LOSClient(apiKey);
String applications = client.listApplications();Active Loan Servicing APIs Authentication
Active Loan Servicing APIs use Bearer JWT authentication. You must include the Bearer prefix before your JWT token.
Header Format
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Token Lifecycle
%%{init: {'theme': 'base', 'themeVariables': {'background': '#ffffff', 'mainBkg': '#ffffff', 'clusterBkg': '#f9fafb', 'clusterBorder': '#2563eb', 'primaryColor': '#2563eb', 'primaryTextColor': '#ffffff', 'titleColor': '#000000', 'lineColor': '#374151'}, 'flowchart': {'padding': 25, 'nodeSpacing': 45, 'rankSpacing': 60}}}%%
flowchart LR
subgraph TOKEN[🎫 JWT Token Lifecycle]
A[🔑 Request] --> B[🎫 Receive]
B --> C[📋 Use]
C --> D{⏰ Expired?}
D -->|No| C
D -->|Yes| A
end
style A fill:#2563eb,stroke:#1e40af,color:#fff
style B fill:#059669,stroke:#047857,color:#fff
style C fill:#2563eb,stroke:#1e40af,color:#fff
style D fill:#6b7280,stroke:#4b5563,color:#fff
| Property | Value |
|---|---|
| Token Type | JWT (JSON Web Token) |
| Expiration | 1 hour (3600 seconds) |
| Refresh Strategy | Request new token before expiry |
Making Authenticated Requests
curl -X GET "https://api.demo.lms.lendfoundry.com/v1/loan-management/search/LN-2024-001234/details" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"const getLoanDetails = async (loanNumber, jwtToken) => {
const response = await fetch(
`https://api.demo.lms.lendfoundry.com/v1/loan-management/search/${loanNumber}/details`,
{
method: 'GET',
headers: {
'Authorization': `Bearer ${jwtToken}`, // Bearer prefix required
'Content-Type': 'application/json'
}
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
};
// Usage
const jwtToken = await getValidToken();
const loan = await getLoanDetails('LN-2024-001234', jwtToken);import requests
import os
def get_loan_details(loan_number, jwt_token):
"""Get loan details from LMS API."""
url = f"https://api.demo.lms.lendfoundry.com/v1/loan-management/search/{loan_number}/details"
headers = {
"Authorization": f"Bearer {jwt_token}", # Bearer prefix required
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json()
# Usage
jwt_token = get_valid_token()
loan = get_loan_details('LN-2024-001234', jwt_token)public class LMSClient {
private final String jwtToken;
private final HttpClient client;
public LMSClient(String jwtToken) {
this.jwtToken = jwtToken;
this.client = HttpClient.newHttpClient();
}
public String getLoanDetails(String loanNumber) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.demo.lms.lendfoundry.com/v1/loan-management/search/" + loanNumber + "/details"))
.header("Authorization", "Bearer " + jwtToken) // Bearer prefix required
.header("Content-Type", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
return response.body();
}
}
// Usage
String jwtToken = getValidToken();
LMSClient client = new LMSClient(jwtToken);
String loanDetails = client.getLoanDetails("LN-2024-001234");Token Management
Implementing Token Refresh
Since JWT tokens expire after 1 hour, implement proactive token refresh:
from datetime import datetime, timedelta
import requests
class TokenManager:
"""Manage JWT token lifecycle with automatic refresh."""
def __init__(self, client_id, client_secret, base_url):
self.client_id = client_id
self.client_secret = client_secret
self.base_url = base_url
self.token = None
self.token_expires_at = None
def get_valid_token(self):
"""Get a valid token, refreshing if necessary."""
# Check if token exists and is valid (with 5 min buffer)
if self.token and self.token_expires_at:
buffer_time = self.token_expires_at - timedelta(minutes=5)
if datetime.now() < buffer_time:
return self.token
# Token expired or doesn't exist - get new one
return self._refresh_token()
def _refresh_token(self):
"""Request a new token from the OAuth endpoint."""
response = requests.post(
f"{self.base_url}/oauth/token",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
)
response.raise_for_status()
token_data = response.json()
self.token = token_data["access_token"]
expires_in = token_data.get("expires_in", 3600)
self.token_expires_at = datetime.now() + timedelta(seconds=expires_in)
return self.token
# Usage
token_manager = TokenManager(
client_id=os.getenv('LENDFOUNDRY_LMS_CLIENT_ID'),
client_secret=os.getenv('LENDFOUNDRY_LMS_CLIENT_SECRET'),
base_url="https://api.demo.lms.lendfoundry.com"
)
# Always get a valid token before making requests
token = token_manager.get_valid_token()Common Mistakes
| ❌ Wrong | ✅ Correct | Error |
|---|---|---|
LOS with Bearer: Authorization: Bearer abc123 | LOS: Authorization: abc123 | 401 Unauthorized |
LMS without Bearer: Authorization: eyJhbG... | LMS: Authorization: Bearer eyJhbG... | 401 Unauthorized |
| Expired JWT token | Refresh token before expiry | 401 Token Expired |
Extra spaces: Bearer eyJhbG... | Single space: Bearer eyJhbG... | 401 Invalid Token |
Error Responses
401 Unauthorized
Cause: Invalid or missing credentials
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing authentication credentials",
"trace_id": "a1b2c3d4e5"
}
}Resolution:
- Verify API key or JWT token is correct
- Check header format matches system requirements
- Ensure token has not expired (LMS only)
403 Forbidden
Cause: Insufficient permissions
{
"error": {
"code": "FORBIDDEN",
"message": "Insufficient permissions to access this resource",
"trace_id": "a1b2c3d4e5"
}
}Resolution:
- Contact support to verify account permissions
- Check if using correct environment credentials
Security Best Practices
✅ DO
| Practice | Example |
|---|---|
| Use environment variables | os.getenv('LENDFOUNDRY_API_KEY') |
| Implement token refresh | Refresh JWT 5 minutes before expiry |
| Use HTTPS only | All LendFoundry APIs require HTTPS |
| Store credentials securely | Use secrets management (AWS Secrets Manager, HashiCorp Vault) |
❌ DON'T
| Anti-Pattern | Risk |
|---|---|
| Hardcode credentials in code | Credentials in version control |
| Share API keys | Unauthorized access |
| Ignore token expiration | Failed requests after 1 hour |
| Log full tokens | Security exposure in logs |
Secure Credential Storage
import os
# ✅ Good - Environment variables
api_key = os.getenv('LENDFOUNDRY_LOS_API_KEY')
client_id = os.getenv('LENDFOUNDRY_LMS_CLIENT_ID')
client_secret = os.getenv('LENDFOUNDRY_LMS_CLIENT_SECRET')
# ✅ Better - Secrets manager (AWS example)
import boto3
def get_secret(secret_name):
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId=secret_name)
return response['SecretString']
api_key = get_secret('lendfoundry/los-api-key')Getting API Credentials
Request Process
- Contact Sales: Email [email protected]
- Specify Requirements:
- Company name
- System(s) needed (Application Submission, Active Loan Servicing, or both)
- Environment (sandbox/production)
- Receive Credentials: Securely delivered through your onboarding process
- Store Securely: Use environment variables or secrets management
Test Your Authentication
Test Application Submission APIs
curl -X POST "https://loc.demo.kendra.lendfoundry.com/v1/darbaan/back-office/rest/api/list-applications" \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"page": 1, "size": 1}'Expected Response: 200 OK with application list
Test Active Loan Servicing APIs
curl -X GET "https://api.demo.lms.lendfoundry.com/v1/loan-management/search/TEST123/details" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json"Expected Response: 200 OK with loan details or 404 Not Found if loan doesn't exist
Next Steps
| Resource | Description |
|---|---|
| Authentication Troubleshooting | Resolve common authentication issues |
| API Integration Flow | Understand the complete integration workflow |
| Rate Limiting | Learn about rate limits and best practices |
Authentication Setup Complete?Head to the API Integration Flow to understand how to integrate both systems.
