🚨 Error Handling

The Dutchie Point of Sale API uses standard HTTP status codes and provides detailed error messages to help you diagnose and resolve issues quickly.

📊 HTTP Status Codes

Our API returns standard HTTP status codes to indicate the success or failure of requests:

✅ Success Codes (2xx)

Code Status Description When You'll See It
200 OK Request succeeded GET requests returning data successfully
201 Created Resource created successfully POST requests that create new resources
204 No Content Request succeeded, no content returned DELETE requests or updates with no response body

❌ Client Error Codes (4xx)

Code Status Common Causes How to Fix
400 Bad Request Invalid request format, missing required fields Check request body format and required parameters
401 Unauthorized Missing or invalid API key Verify API key is correct and properly encoded
403 Forbidden Valid API key but insufficient permissions Contact support to verify API key permissions
404 Not Found Resource doesn't exist or incorrect endpoint Check endpoint URL and resource ID
422 Unprocessable Entity Valid format but business logic validation failed Review validation errors in response body
429 Too Many Requests Rate limit exceeded Implement exponential backoff and retry logic

🔧 Server Error Codes (5xx)

Code Status What It Means What to Do
500 Internal Server Error Unexpected server-side error or bug Log error; contact support (do not retry)
502 Bad Gateway Gateway/proxy server issue Retry with exponential backoff
503 Service Unavailable Service temporarily down for maintenance Check status page; retry after specified time

📋 Error Response Format

The API uses standard HTTP status codes to indicate success or failure. Error responses vary by error type:

Authentication Errors (401)

Authentication failures return empty response body with HTTP 401 status.

HTTP/1.1 401 Unauthorized
Content-Length: 0

Validation Errors (400)

The API uses five distinct response formats for validation errors, each optimized for different error scenarios. Clients should handle the specific response format for the endpoints they are calling:

1. Empty Response (void)

How to Identify

In the API documentation, look for 400 Bad Request responses marked as void.

Overview

Used for simple validation failures where no error details are needed. You'll encounter this with basic operations that have binary success/failure states, such as plant operations and package operations. This format provides minimal overhead with no response body, making it the most efficient option for simple validation scenarios.

HTTP Response Example
HTTP/1.1 400 Bad Request
Content-Length: 0
JavaScript Implementation Example
async function handleVoidError(endpoint, options = {}) {
  try {
    const response = await fetch(`https://api.pos.dutchie.com${endpoint}`, {
      headers: {
        'Authorization': `Basic ${btoa(apiKey + ':')}`,
        'Content-Type': 'application/json'
      },
      ...options
    });

    if (response.status === 400) {
      // Empty response body - just status code information
      throw new APIError(400, 'Operation validation failed');
    }

    if (!response.ok) {
      throw new APIError(response.status, `HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

2. String Response (Simple Message)

How to Identify

In the API documentation, look for 400 Bad Request responses with schema type string or "Response body: string".

Overview

Used for business rule violations that require a single, descriptive error message. You'll encounter this with pre-order status validation, pricing calculations, and strain operations. Examples include messages like "Order is no longer in an cancellable state" or "Invalid strain configuration". This format provides simple string responses that are human-readable and require minimal parsing.

HTTP Response Example
HTTP/1.1 400 Bad Request
"Please request fewer than 10000 customers at a time"
JavaScript Implementation Example
async function handleStringError(endpoint, options = {}) {
  try {
    const response = await fetch(`https://api.pos.dutchie.com${endpoint}`, {
      headers: {
        'Authorization': `Basic ${btoa(apiKey + ':')}`,
        'Content-Type': 'application/json'
      },
      ...options
    });

    if (response.status === 400) {
      const errorMessage = await response.text();
      throw new APIError(400, errorMessage);
    }

    if (!response.ok) {
      throw new APIError(response.status, `HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

3. BadRequestResponse Format (Simple Message)

How to Identify

In the API documentation, look for 400 Bad Request responses with schema type BadRequestResponse showing message field with propertyErrors as null or empty.

Overview

Used for business logic violations that need a single descriptive message in a structured format. You'll encounter this with customer operations, product validation, and business rule enforcement. This format provides a consistent wrapper with a single error message and no field-specific details, making it ideal for high-level validation failures.

HTTP Response Example
HTTP/1.1 400 Bad Request
{
  "message": "Customer is null or unparseable",
  "propertyErrors": null
}
JavaScript Implementation Example
async function handleBadRequestSimple(endpoint, options = {}) {
  try {
    const response = await fetch(`https://api.pos.dutchie.com${endpoint}`, {
      headers: {
        'Authorization': `Basic ${btoa(apiKey + ':')}`,
        'Content-Type': 'application/json'
      },
      ...options
    });

    if (response.status === 400) {
      const errorData = await response.json();
      
      // Verify this is the simple message format (propertyErrors should be null)
      if (errorData.propertyErrors === null || errorData.propertyErrors === undefined) {
        throw new APIError(400, errorData.message || 'Validation failed');
      } else {
        // This endpoint returned field-specific errors unexpectedly
        throw new APIError(400, 'Unexpected field errors in simple message endpoint');
      }
    }

    if (!response.ok) {
      throw new APIError(response.status, `HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

4. BadRequestResponse Format (Field-Specific Errors)

How to Identify

In the API documentation, look for 400 Bad Request responses with schema type BadRequestResponse showing message and populated propertyErrors array.

Overview

Used for form validation with multiple field-level errors in a simplified structure. You'll encounter this with customer registration, data entry forms, and multi-field validation scenarios. This format provides a structured wrapper with field-specific errors using simplified error details, making it easy to highlight specific form fields that need correction.

HTTP Response Example
HTTP/1.1 400 Bad Request
{
  "message": "Errors found. Check PropertyErrors.",
  "propertyErrors": [
    {
      "propertyName": "Email",
      "propertyError": "Email address is required"
    },
    {
      "propertyName": "DateOfBirth",
      "propertyError": "Date must be between 1800 and current date"
    }
  ]
}
JavaScript Implementation Example
async function handleBadRequestFieldErrors(endpoint, options = {}) {
  try {
    const response = await fetch(`https://api.pos.dutchie.com${endpoint}`, {
      headers: {
        'Authorization': `Basic ${btoa(apiKey + ':')}`,
        'Content-Type': 'application/json'
      },
      ...options
    });

    if (response.status === 400) {
      const errorData = await response.json();
      
      if (errorData.propertyErrors && errorData.propertyErrors.length > 0) {
        console.log('Field-specific errors:');
        errorData.propertyErrors.forEach(error => {
          console.log(`${error.propertyName}: ${error.propertyError}`);
        });
      }
      
      throw new APIError(400, errorData.message || 'Validation failed');
    }

    if (!response.ok) {
      throw new APIError(response.status, `HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

5. ValidationResult Format

How to Identify

In the API documentation, look for 400 Bad Request responses with schema type ValidationResult or showing isValid, errors, and ruleSetsExecuted fields.

Overview

Used for comprehensive validation with detailed metadata and multiple field errors. You'll encounter this with driver management, vehicle management, delivery operations, package tagging, and inventory transfers. Examples include driver registration forms, vehicle certification, and delivery route validation. This format provides the most detailed error information including error codes, attempted values, severity levels, and validation context.

HTTP Response Example
HTTP/1.1 400 Bad Request
{
  "isValid": false,
  "errors": [
    {
      "propertyName": "Email",
      "errorMessage": "Email address is required",
      "attemptedValue": "",
      "customState": null,
      "severity": 0,
      "errorCode": "NotEmptyValidator",
      "formattedMessagePlaceholderValues": {
        "PropertyName": "Email",
        "PropertyValue": ""
      }
    },
    {
      "propertyName": "PhoneNumber",
      "errorMessage": "Phone number format is invalid",
      "attemptedValue": "123-abc-7890",
      "customState": null,
      "severity": 0,
      "errorCode": "RegularExpressionValidator",
      "formattedMessagePlaceholderValues": {
        "PropertyName": "PhoneNumber",
        "PropertyValue": "123-abc-7890"
      }
    },
    {
      "propertyName": "DateOfBirth", 
      "errorMessage": "Date of birth cannot be in the future",
      "attemptedValue": "2025-01-15",
      "customState": null,
      "severity": 0,
      "errorCode": "LessThanOrEqualValidator",
      "formattedMessagePlaceholderValues": {
        "PropertyName": "DateOfBirth",
        "PropertyValue": "2025-01-15",
        "ComparisonValue": "2024-12-20"
      }
    },
    {
      "propertyName": "LoyaltyNumber",
      "errorMessage": "Loyalty number must be exactly 8 digits",
      "attemptedValue": "12345",
      "customState": null,
      "severity": 0,
      "errorCode": "ExactLengthValidator",
      "formattedMessagePlaceholderValues": {
        "PropertyName": "LoyaltyNumber",
        "PropertyValue": "12345",
        "MaxLength": "8"
      }
    }
  ],
  "ruleSetsExecuted": []
}
JavaScript Implementation Example
async function handleValidationResult(endpoint, options = {}) {
  try {
    const response = await fetch(`https://api.pos.dutchie.com${endpoint}`, {
      headers: {
        'Authorization': `Basic ${btoa(apiKey + ':')}`,
        'Content-Type': 'application/json'
      },
      ...options
    });

    if (response.status === 400) {
      const validationResult = await response.json();
      
      if (validationResult.isValid === false && validationResult.errors) {
        console.log('Detailed validation errors:');
        validationResult.errors.forEach(error => {
          console.log(`Field: ${error.propertyName}`);
          console.log(`Error: ${error.errorMessage}`);
          console.log(`Code: ${error.errorCode}`);
        });
        
        const errorMessages = validationResult.errors.map(e => 
          `${e.propertyName}: ${e.errorMessage}`
        );
        throw new APIError(400, `Validation failed: ${errorMessages.join(', ')}`);
      }
    }

    if (!response.ok) {
      throw new APIError(response.status, `HTTP ${response.status}: ${response.statusText}`);
    }

    return await response.json();
  } catch (error) {
    console.error('API Error:', error);
    throw error;
  }
}

Not Found Errors (404)

Resource not found errors return empty response body with HTTP 404 status.

HTTP/1.1 404 Not Found
Content-Length: 0

Server Errors (500)

Internal server errors return empty response body with HTTP 500 status.

HTTP/1.1 500 Internal Server Error
Content-Length: 0

🛠️ Error Handling Best Practices

1. Implement Retry Logic

async function apiWithRetry(endpoint, options = {}, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await callAPI(endpoint, options);
    } catch (error) {
      lastError = error;
      
      // Don't retry on client errors (4xx except 429) or 500 Internal Server Error
      if ((error.status >= 400 && error.status < 500 && error.status !== 429) || error.status === 500) {
        throw error;
      }
      
      // Calculate exponential backoff delay
      const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
      
      if (attempt < maxRetries) {
        console.log(`Retrying after ${delay}ms... (attempt ${attempt + 1}/${maxRetries})`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

2. Handle Specific Error Types

function handleAPIError(error) {
  switch (error.status) {
    case 401:
      // Redirect to authentication
      window.location.href = '/login';
      break;
      
    case 403:
      showError('You don\'t have permission to perform this action.');
      break;
      
    case 404:
      showError('The requested resource was not found.');
      break;
      
    case 400:
      // Check the endpoint's documentation for the specific 400 response format
      showError('Bad request. Check the endpoint documentation for the specific error format.');
      break;
      
    case 422:
      // Unprocessable entity
      showError('The request was valid but could not be processed.');
      break;
      
    case 429:
      showError('Too many requests. Please wait before trying again.');
      break;
      
    case 500:
      showError('Server error occurred. Please contact support.');
      break;
      
    case 502:
    case 503:
      showError('Service temporarily unavailable. Please try again later.');
      break;
      
    default:
      showError('An unexpected error occurred. Please try again.');
  }
}

🔗 Related Resources