Error Handling
Error Handling
Section titled “Error Handling”The Quickli Public API uses standard HTTP status codes and a consistent error response format to help you diagnose and handle errors effectively.
Error Response Format
Section titled “Error Response Format”All errors follow this structure:
{ error: { code: string, // Machine-readable error code message : string, // Human-readable error message details ? : unknown, // Optional additional error details timestamp : string, // ISO 8601 timestamp path ? : string // Request path that caused the error }}Example Error Response
Section titled “Example Error Response”{ "error": { "code": "VALIDATION_ERROR", "message": "Invalid request body", "details": { "issues": [ { "path": [ "scenario", "households" ], "message": "Expected array, received null" } ] }, "timestamp": "2025-11-03T10:30:00.000Z", "path": "/api/v1/scenarios" }}HTTP Status Codes
Section titled “HTTP Status Codes”2xx Success
Section titled “2xx Success”| Code | Status | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
4xx Client Errors
Section titled “4xx Client Errors”| Code | Status | Description |
|---|---|---|
| 400 | Bad Request | Invalid request format or parameters |
| 401 | Unauthorized | Missing, invalid, or expired credentials |
| 403 | Forbidden | Authenticated but not authorized for this resource |
| 404 | Not Found | Resource doesn’t exist or you don’t have access |
| 405 | Method Not Allowed | HTTP method not supported for this endpoint |
| 422 | Unprocessable Entity | Validation failed for request body |
| 429 | Too Many Requests | Rate limit exceeded (not yet enforced) |
5xx Server Errors
Section titled “5xx Server Errors”| Code | Status | Description |
|---|---|---|
| 500 | Internal Server Error | Unexpected server error |
Exception Types
Section titled “Exception Types”BadRequestException (400)
Section titled “BadRequestException (400)”Invalid request format, malformed data, or missing required parameters.
Common causes:
- Invalid JSON in request body
- Missing required query parameters
- Invalid data types
Example:
{ "error": { "code": "BAD_REQUEST", "message": "Invalid query parameter: lenders must be provided", "timestamp": "2025-11-03T10:30:00.000Z" }}How to fix: Check the error message for specific details about what’s wrong with your request.
UnauthorizedException (401)
Section titled “UnauthorizedException (401)”Authentication failed - credentials are missing, invalid, or signature verification failed.
Common causes:
- Missing required authentication headers (
X-Auth-Client-ID,X-Auth-Access-Token,X-Auth-Timestamp,X-Auth-Nonce,X-Auth-Signature) - Invalid client identifier
- Invalid or expired access token
- Invalid signature (signature verification failed)
- User not found or inactive
Example - Invalid Signature:
{ "error": { "code": "INVALID_SIGNATURE", "message": "Request signature verification failed", "timestamp": "2025-11-19T10:30:00.000Z" }}Example - Invalid Client:
{ "error": { "code": "UNAUTHORIZED", "message": "Invalid client identifier", "timestamp": "2025-11-19T10:30:00.000Z" }}How to fix:
- Verify all five authentication headers are present
- Check that your client ID is correct and registered
- Verify your signature implementation matches the canonical request format
- Ensure empty bodies are hashed as empty strings
- Contact support if you believe your credentials should be valid
ForbiddenException (403)
Section titled “ForbiddenException (403)”Authenticated successfully but not authorized to access this resource.
Common causes:
- Accessing a team you don’t have permission for
- Requesting a lender you don’t have access to
- Attempting operations outside your organization scope
Example:
{ "error": { "code": "FORBIDDEN", "message": "User does not have access to team: 507f1f77bcf86cd799439011", "timestamp": "2025-11-03T10:30:00.000Z" }}How to fix:
- Use
GET /api/v1/userto see which teams you have access to - Verify you’re using a
teamIdfrom your accessible teams - Contact your administrator to request additional permissions
NotFoundException (404)
Section titled “NotFoundException (404)”Resource doesn’t exist, or you don’t have permission to see it exists.
Common causes:
- Invalid scenario ID
- Scenario exists but belongs to a team you can’t access
- Endpoint doesn’t exist (check the URL)
Example:
{ "error": { "code": "NOT_FOUND", "message": "Scenario not found: 507f1f77bcf86cd799439011", "timestamp": "2025-11-03T10:30:00.000Z" }}Security note: For privacy reasons, we return 404 (not 403) when you try to access a resource that exists but you don’t have permission for. This prevents information leakage about what resources exist.
How to fix:
- Verify the resource ID is correct
- Check that you have access to the team that owns the resource
- Confirm the resource hasn’t been deleted
MethodNotAllowedException (405)
Section titled “MethodNotAllowedException (405)”The HTTP method is not supported for this endpoint.
Example:
{ "error": { "code": "METHOD_NOT_ALLOWED", "message": "Method PATCH not allowed. Allowed methods: GET, PUT", "timestamp": "2025-11-03T10:30:00.000Z" }}How to fix: Use one of the allowed HTTP methods listed in the error message.
ValidationException (422)
Section titled “ValidationException (422)”Request body failed Zod schema validation.
Common causes:
- Missing required fields
- Wrong data types
- Values outside allowed ranges
- Invalid enum values
Example:
{ "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": { "issues": [ { "path": [ "lenderName" ], "message": "Invalid enum value. Expected 'cba' | 'westpac' | 'anz' | ..., received 'invalid_lender'" }, { "path": [ "addToRates" ], "message": "Expected number, received string" } ] }, "timestamp": "2025-11-03T10:30:00.000Z" }}How to fix:
- Review the
issuesarray to see which fields failed validation - Check the schema documentation for correct field types and constraints
- Fix each issue and retry the request
RateLimitException (429)
Section titled “RateLimitException (429)”You’ve exceeded the rate limit for your client.
Note: Rate limiting is not currently enforced but will be in future releases.
Example:
{ "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded. Please try again later.", "details": { "retryAfter": 60 }, "timestamp": "2025-11-03T10:30:00.000Z" }}How to fix: Implement exponential backoff and respect the retryAfter value.
InternalServerException (500)
Section titled “InternalServerException (500)”An unexpected error occurred on the server.
Example:
{ "error": { "code": "INTERNAL_SERVER_ERROR", "message": "An unexpected error occurred", "timestamp": "2025-11-03T10:30:00.000Z" }}How to fix:
- Retry the request (it may be a transient issue)
- If the problem persists, contact support with the timestamp
- Check status page for known incidents
Error Handling Best Practices
Section titled “Error Handling Best Practices”1. Always Check Response Status
Section titled “1. Always Check Response Status”const response = await fetch('https://api.quickli.com/api/v1/scenarios', { method: 'POST', headers: { 'Authorization': `Bearer ${credentials}`, 'Content-Type': 'application/json' }, body: JSON.stringify(data)});
if (!response.ok) { const error = await response.json(); throw new Error(`API Error (${response.status}): ${error.error.message}`);}
const result = await response.json();2. Handle Different Error Types
Section titled “2. Handle Different Error Types”try { const data = await apiRequest('/api/v1/scenarios', {method: 'POST', ...}); return data;} catch (error) { if (error.status === 401) { // Check error code for specific handling if (error.error?.code === 'INVALID_SIGNATURE') { // Signature verification failed - check signing implementation console.error('Signature verification failed'); } throw error; // Re-throw auth errors } else if (error.status === 422) { // Show validation errors to user showValidationErrors(error.details.issues); } else if (error.status === 500) { // Log to error tracking service logError(error); showGenericError(); } else { // Handle other errors throw error; }}3. Implement Exponential Backoff
Section titled “3. Implement Exponential Backoff”async function apiRequestWithRetry( url: string, options: RequestInit, maxRetries = 3): Promise<any> { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, options);
if (response.ok) { return await response.json(); }
// Don't retry client errors (except 429) if (response.status >= 400 && response.status < 500 && response.status !== 429) { throw await response.json(); }
// Retry on 429 or 5xx if (i < maxRetries - 1) { const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); continue; }
throw await response.json(); } catch (error) { if (i === maxRetries - 1) throw error; } }}4. Log Errors with Context
Section titled “4. Log Errors with Context”async function apiRequest(url: string, options: RequestInit) { try { const response = await fetch(url, options);
if (!response.ok) { const error = await response.json();
// Log with full context console.error('API Error:', { url, method: options.method, status: response.status, error: error.error, timestamp: new Date().toISOString() });
throw error; }
return await response.json(); } catch (error) { // Log network errors too console.error('Network Error:', { url, error: error.message }); throw error; }}5. Provide User-Friendly Error Messages
Section titled “5. Provide User-Friendly Error Messages”function getUserFriendlyMessage(error: ApiError): string { switch (error.error.code) { case 'UNAUTHORIZED': return 'Your session has expired. Please log in again.'; case 'FORBIDDEN': return 'You don\'t have permission to access this resource.'; case 'NOT_FOUND': return 'The requested resource was not found.'; case 'VALIDATION_ERROR': return 'Please check your input and try again.'; case 'RATE_LIMIT_EXCEEDED': return 'Too many requests. Please wait a moment and try again.'; case 'INTERNAL_SERVER_ERROR': return 'Something went wrong on our end. Please try again later.'; default: return 'An unexpected error occurred. Please try again.'; }}Common Error Scenarios
Section titled “Common Error Scenarios”Scenario 1: Creating a Scenario with Invalid Team
Section titled “Scenario 1: Creating a Scenario with Invalid Team”Request:
curl -X POST https://api.quickli.com/api/v1/scenarios \ -H "Authorization: Bearer {credentials}" \ -H "Content-Type: application/json" \ -d '{"teamId": "invalid-team-id", "scenario": {}}'Response (403):
{ "error": { "code": "FORBIDDEN", "message": "User does not have access to team: invalid-team-id", "timestamp": "2025-11-03T10:30:00.000Z" }}Solution: Use GET /api/v1/user to get your accessible teams, then use one of those team IDs.
Scenario 2: Computing Servicing with Invalid Lender
Section titled “Scenario 2: Computing Servicing with Invalid Lender”Request:
curl -X POST https://api.quickli.com/api/v1/compute/507f1f77bcf86cd799439011 \ -H "Authorization: Bearer {credentials}" \ -H "Content-Type: application/json" \ -d '{"lenderName": "unknown_lender"}'Response (422):
{ "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": { "issues": [ { "path": [ "lenderName" ], "message": "Invalid enum value. Expected 'cba' | 'westpac' | 'anz' | ..., received 'unknown_lender'" } ] }, "timestamp": "2025-11-03T10:30:00.000Z" }}Solution: Check the valid lender names in the compute endpoint docs.
Scenario 3: Invalid Signature
Section titled “Scenario 3: Invalid Signature”Request:
curl -X GET https://api.quickli.com/api/v1/user \ -H "X-Auth-Client-ID: Loan Market Group" \ -H "X-Auth-Access-Token: abc123-uuid" \ -H "X-Auth-Timestamp: 2025-11-19T10:30:00.000Z" \ -H "X-Auth-Nonce: 550e8400-e29b-41d4-a716-446655440000" \ -H "X-Auth-Signature: invalid-signature"Response (401):
{ "error": { "code": "INVALID_SIGNATURE", "message": "Request signature verification failed", "timestamp": "2025-11-19T10:30:00.000Z" }}Solution: Verify your signature implementation matches the canonical request format. Check empty body handling (GET requests should hash an empty string).
Debugging Tips
Section titled “Debugging Tips”1. Use the Echo Endpoint
Section titled “1. Use the Echo Endpoint”Test your authentication without side effects:
curl -X POST https://api.quickli.com/api/v1/echo \ -H "Authorization: Bearer {credentials}" \ -H "Content-Type: application/json" \ -d '{"test": "data"}'This echoes back your request and confirms authentication works.
2. Check the OpenAPI Spec
Section titled “2. Check the OpenAPI Spec”Download the spec to see expected schemas:
curl https://api.quickli.com/api/v1/openapi.json > openapi.json3. Test with Swagger UI
Section titled “3. Test with Swagger UI”Use the interactive documentation at https://api.quickli.com/docs to test requests and see response formats.
4. Enable Verbose Logging
Section titled “4. Enable Verbose Logging”Log full request and response details (but mask sensitive data):
async function debugApiRequest(url: string, options: RequestInit) { console.log('Request:', { url, method: options.method, headers: maskSensitiveHeaders(options.headers), body: options.body });
const response = await fetch(url, options); const data = await response.json();
console.log('Response:', { status: response.status, statusText: response.statusText, data });
return data;}Support
Section titled “Support”If you encounter errors you can’t resolve:
- Check Status Page: Verify there are no known incidents
- Review Documentation: Ensure you’re using the API correctly
- Contact Support: Email hello@quickli.com.au with:
- Error timestamp
- Request details (mask sensitive data)
- What you were trying to accomplish
- Steps to reproduce
- Contact Support via Slack if possible.
- This will often result in the fastest response time from Engineers.
Last updated: 2025-11-19