Error Handling
Learn how to handle errors gracefully when integrating with the Matchstra API.
Error Response Format
All errors follow a consistent structure:
{
"success": false,
"message": "Human-readable error description",
"data": null,
"errors": [
"Detailed error message 1",
"Detailed error message 2"
]
}
Fields:
success: Alwaysfalsefor errorsmessage: Summary of the errordata: Alwaysnullfor errorserrors: Array of specific error details (may benullfor some errors)
HTTP Status Codes
Matchstra uses standard HTTP status codes:
| Code | Name | Meaning | Action |
|---|---|---|---|
| 200 | OK | Request succeeded | Process response data |
| 400 | Bad Request | Invalid request payload | Fix request parameters |
| 401 | Unauthorized | Invalid or missing API key | Check authentication |
| 403 | Forbidden | Access denied (e.g., IP not whitelisted) | Check permissions |
| 404 | Not Found | Resource doesn't exist | Verify resource ID |
| 429 | Too Many Requests | Rate limit exceeded | Implement backoff |
| 500 | Internal Server Error | Server-side issue | Retry later |
| 503 | Service Unavailable | Maintenance or overload | Retry with backoff |
Common Errors
400 Bad Request
Scenario 1: Missing Required Fields
{
"success": false,
"message": "Validation failed",
"data": null,
"errors": [
"The Name field is required.",
"The EntityType field is required."
]
}
Solution: Validate input before sending requests.
function validateScreeningRequest(data) {
const errors = [];
if (!data.name || data.name.trim() === '') {
errors.push('Name is required');
}
if (!data.entityType) {
errors.push('EntityType is required');
} else if (!['Individual', 'Organization'].includes(data.entityType)) {
errors.push('EntityType must be "Individual" or "Organization"');
}
if (data.minScore !== undefined && (data.minScore < 0 || data.minScore > 100)) {
errors.push('minScore must be between 0 and 100');
}
return errors;
}
// Usage
const errors = validateScreeningRequest(requestData);
if (errors.length > 0) {
console.error('Validation errors:', errors);
return;
}
// Proceed with API call
const response = await fetch(url, { ... });
Scenario 2: Invalid Data Format
{
"success": false,
"message": "Invalid request format",
"data": null,
"errors": [
"DateOfBirth must be in ISO 8601 format (e.g., 2000-01-15T00:00:00Z)"
]
}
Solution: Ensure proper date formatting.
function formatDateForAPI(dateString) {
const date = new Date(dateString);
return date.toISOString(); // Returns format: 2000-01-15T00:00:00.000Z
}
// Usage
const requestData = {
name: 'John Doe',
dateOfBirth: formatDateForAPI('2000-01-15'),
entityType: 'Individual'
};
Scenario 3: File Upload Issues
{
"success": false,
"message": "Invalid file upload",
"data": null,
"errors": [
"IdCardImage: File size exceeds 10 MB limit",
"SelfieImage: Invalid image format. Only JPEG and PNG are supported."
]
}
Solution: Validate files before upload.
function validateImageFile(file, maxSizeMB = 10) {
const errors = [];
// Check file type
const validTypes = ['image/jpeg', 'image/png'];
if (!validTypes.includes(file.type)) {
errors.push(`Invalid format. Only JPEG and PNG are supported.`);
}
// Check file size
const maxSize = maxSizeMB * 1024 * 1024; // Convert to bytes
if (file.size > maxSize) {
errors.push(`File size (${(file.size / 1024 / 1024).toFixed(2)} MB) exceeds ${maxSizeMB} MB limit`);
}
return errors;
}
// Usage
const idCardErrors = validateImageFile(idCardFile);
const selfieErrors = validateImageFile(selfieFile);
if (idCardErrors.length > 0 || selfieErrors.length > 0) {
console.error('Image validation failed:', [...idCardErrors, ...selfieErrors]);
return;
}
// Proceed with upload
401 Unauthorized
Scenario: Invalid API Key
{
"success": false,
"message": "Unauthorized",
"data": null,
"errors": ["Invalid or missing API key"]
}
Solution: Verify API key is correctly set.
async function makeAuthenticatedRequest(url, options = {}) {
const apiKey = process.env.MATCHSTRA_API_KEY;
if (!apiKey) {
throw new Error('MATCHSTRA_API_KEY environment variable not set');
}
const headers = {
'X-API-Key': apiKey,
...options.headers
};
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
throw new Error('Authentication failed. Please verify your API key is valid and not revoked.');
}
return response;
}
403 Forbidden
Scenario: IP Not Whitelisted
{
"success": false,
"message": "Forbidden",
"data": null,
"errors": ["Request origin not in IP whitelist"]
}
Solution: Add your IP to the whitelist in the dashboard or use an unrestricted key.
404 Not Found
Scenario: Resource Doesn't Exist
{
"success": false,
"message": "Not Found",
"data": null,
"errors": ["Verification with ID 99999 not found"]
}
Solution: Verify the ID exists and belongs to your account.
async function getVerification(id) {
try {
const response = await fetch(
`https://api.matchstra.ca/api/IdVerification/${id}`,
{
headers: { 'X-API-Key': process.env.MATCHSTRA_API_KEY }
}
);
if (response.status === 404) {
console.error(`Verification ${id} not found`);
return null;
}
return await response.json();
} catch (error) {
console.error('Error fetching verification:', error);
return null;
}
}
429 Too Many Requests
Scenario: Rate Limit Exceeded
{
"success": false,
"message": "Rate limit exceeded",
"data": null,
"errors": ["Request quota exhausted. Retry after 60 seconds."]
}
Headers:
Retry-After: 60
Solution: Implement exponential backoff and respect Retry-After header.
async function fetchWithRetry(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', 10);
console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue; // Retry
}
return response; // Success or non-retryable error
}
throw new Error('Max retries exceeded');
}
500 Internal Server Error
Scenario: Server-Side Issue
{
"success": false,
"message": "Internal server error",
"data": null,
"errors": ["An unexpected error occurred"]
}
Solution: Implement retry logic with exponential backoff.
async function fetchWithExponentialBackoff(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
// Retry on 5xx errors
if (response.status >= 500) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s, ...
console.log(`Server error. Retrying in ${delay/1000}s...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
return response; // Success or non-retryable error
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = Math.pow(2, attempt) * 1000;
console.log(`Network error. Retrying in ${delay/1000}s...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}
Comprehensive Error Handler
Here's a production-ready error handler:
class MatchstraAPIError extends Error {
constructor(message, statusCode, errors = []) {
super(message);
this.name = 'MatchstraAPIError';
this.statusCode = statusCode;
this.errors = errors;
}
}
async function callMatchstraAPI(url, options = {}) {
const apiKey = process.env.MATCHSTRA_API_KEY;
if (!apiKey) {
throw new Error('MATCHSTRA_API_KEY not configured');
}
const headers = {
'X-API-Key': apiKey,
...options.headers
};
let response;
try {
response = await fetch(url, { ...options, headers });
} catch (error) {
// Network error
throw new MatchstraAPIError(
'Network error: Unable to reach Matchstra API',
0,
[error.message]
);
}
// Parse response
let data;
try {
data = await response.json();
} catch (error) {
throw new MatchstraAPIError(
'Invalid JSON response from server',
response.status,
['Unable to parse response body']
);
}
// Handle HTTP errors
if (!response.ok) {
const errorMessage = data.message || `HTTP ${response.status} error`;
const errors = data.errors || [];
throw new MatchstraAPIError(errorMessage, response.status, errors);
}
// Handle application errors
if (!data.success) {
throw new MatchstraAPIError(
data.message || 'Request failed',
response.status,
data.errors || []
);
}
return data;
}
// Usage
try {
const result = await callMatchstraAPI(
'https://api.matchstra.ca/api/Screening/screen',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'John Doe',
entityType: 'Individual'
})
}
);
console.log('Success:', result.data);
} catch (error) {
if (error instanceof MatchstraAPIError) {
console.error(`API Error (${error.statusCode}): ${error.message}`);
if (error.errors.length > 0) {
console.error('Details:', error.errors);
}
// Handle specific errors
switch (error.statusCode) {
case 400:
// Show validation errors to user
displayValidationErrors(error.errors);
break;
case 401:
// Redirect to login or show auth error
redirectToLogin();
break;
case 429:
// Show rate limit message
showRateLimitMessage();
break;
case 500:
// Show generic error, log for debugging
showGenericError();
logError(error);
break;
default:
showGenericError();
}
} else {
console.error('Unexpected error:', error);
}
}
Best Practices
1. Always Check HTTP Status
Don't assume a response is successful.
const response = await fetch(url, options);
if (!response.ok) {
const error = await response.json();
throw new Error(error.message || `HTTP ${response.status}`);
}
const data = await response.json();
2. Validate Before Sending
Catch errors client-side before making API calls.
// Bad
const response = await fetch(url, {
body: JSON.stringify({ name: '' }) // Server will reject
});
// Good
if (name.trim() === '') {
showError('Name is required');
return;
}
const response = await fetch(url, {
body: JSON.stringify({ name: name.trim() })
});
3. Implement Retry Logic
For transient errors (429, 500, 503), retry with backoff.
async function withRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
const isRetryable = error.statusCode >= 500 || error.statusCode === 429;
const isLastAttempt = i === maxRetries - 1;
if (!isRetryable || isLastAttempt) {
throw error;
}
const delay = Math.pow(2, i) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
4. Log Errors Appropriately
Log for debugging, but don't expose sensitive data.
function logError(error, context = {}) {
console.error('Matchstra API Error:', {
message: error.message,
statusCode: error.statusCode,
timestamp: new Date().toISOString(),
context: context,
// Don't log API keys, PII, or full request bodies
});
}
5. Provide User-Friendly Messages
Don't show raw API errors to end users.
function getUserFriendlyMessage(error) {
switch (error.statusCode) {
case 400:
return 'Please check your input and try again.';
case 401:
return 'Authentication failed. Please log in again.';
case 403:
return 'You do not have permission to perform this action.';
case 404:
return 'The requested resource was not found.';
case 429:
return 'Too many requests. Please try again in a minute.';
case 500:
case 503:
return 'Service temporarily unavailable. Please try again later.';
default:
return 'An unexpected error occurred. Please contact support if this persists.';
}
}
// Usage
try {
const result = await callAPI();
} catch (error) {
const userMessage = getUserFriendlyMessage(error);
showNotification(userMessage, 'error');
logError(error); // For debugging
}
Monitoring & Alerting
Track error rates to identify issues:
const errorMetrics = {
'400': 0,
'401': 0,
'429': 0,
'500': 0,
'network': 0
};
function trackError(error) {
const key = error.statusCode || 'network';
errorMetrics[key] = (errorMetrics[key] || 0) + 1;
// Alert if error rate is high
const totalErrors = Object.values(errorMetrics).reduce((a, b) => a + b, 0);
if (totalErrors > 10) {
alertOps('High error rate detected', errorMetrics);
}
}
Next Steps
- Review Rate Limits to avoid 429 errors
- Read Authentication Guide to fix 401 errors
- Visit the API Reference for endpoint-specific error details
- Contact Support if you encounter persistent errors