Skip to main content

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: Always false for errors
  • message: Summary of the error
  • data: Always null for errors
  • errors: Array of specific error details (may be null for some errors)

HTTP Status Codes

Matchstra uses standard HTTP status codes:

CodeNameMeaningAction
200OKRequest succeededProcess response data
400Bad RequestInvalid request payloadFix request parameters
401UnauthorizedInvalid or missing API keyCheck authentication
403ForbiddenAccess denied (e.g., IP not whitelisted)Check permissions
404Not FoundResource doesn't existVerify resource ID
429Too Many RequestsRate limit exceededImplement backoff
500Internal Server ErrorServer-side issueRetry later
503Service UnavailableMaintenance or overloadRetry 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