Skip to content

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.

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
}
}
{
"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"
}
}
CodeStatusDescription
200OKRequest succeeded
201CreatedResource created successfully
CodeStatusDescription
400Bad RequestInvalid request format or parameters
401UnauthorizedMissing, invalid, or expired credentials
403ForbiddenAuthenticated but not authorized for this resource
404Not FoundResource doesn’t exist or you don’t have access
405Method Not AllowedHTTP method not supported for this endpoint
422Unprocessable EntityValidation failed for request body
429Too Many RequestsRate limit exceeded (not yet enforced)
CodeStatusDescription
500Internal Server ErrorUnexpected server error

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.


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:

  1. Verify all five authentication headers are present
  2. Check that your client ID is correct and registered
  3. Verify your signature implementation matches the canonical request format
  4. Ensure empty bodies are hashed as empty strings
  5. Contact support if you believe your credentials should be valid

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:

  1. Use GET /api/v1/user to see which teams you have access to
  2. Verify you’re using a teamId from your accessible teams
  3. Contact your administrator to request additional permissions

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:

  1. Verify the resource ID is correct
  2. Check that you have access to the team that owns the resource
  3. Confirm the resource hasn’t been deleted

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.


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:

  1. Review the issues array to see which fields failed validation
  2. Check the schema documentation for correct field types and constraints
  3. Fix each issue and retry the request

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.


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:

  1. Retry the request (it may be a transient issue)
  2. If the problem persists, contact support with the timestamp
  3. Check status page for known incidents
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();
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;
}
}
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;
}
}
}
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;
}
}
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.';
}
}

Scenario 1: Creating a Scenario with Invalid Team

Section titled “Scenario 1: Creating a Scenario with Invalid Team”

Request:

Terminal window
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:

Terminal window
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.


Request:

Terminal window
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).

Test your authentication without side effects:

Terminal window
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.

Download the spec to see expected schemas:

Terminal window
curl https://api.quickli.com/api/v1/openapi.json > openapi.json

Use the interactive documentation at https://api.quickli.com/docs to test requests and see response formats.

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;
}

If you encounter errors you can’t resolve:

  1. Check Status Page: Verify there are no known incidents
  2. Review Documentation: Ensure you’re using the API correctly
  3. Contact Support: Email hello@quickli.com.au with:
  • Error timestamp
  • Request details (mask sensitive data)
  • What you were trying to accomplish
  • Steps to reproduce
  1. Contact Support via Slack if possible.
  • This will often result in the fastest response time from Engineers.

Last updated: 2025-11-19