The Dutchie Point of Sale API uses standard HTTP status codes and provides detailed error messages to help you diagnose and resolve issues quickly.
Our API returns standard HTTP status codes to indicate the success or failure of requests:
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 |
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 |
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 |
The API uses standard HTTP status codes to indicate success or failure. Error responses vary by error type:
Authentication failures return empty response body with HTTP 401 status.
HTTP/1.1 401 Unauthorized
Content-Length: 0
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:
In the API documentation, look for 400 Bad Request responses marked as void
.
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/1.1 400 Bad Request
Content-Length: 0
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;
}
}
In the API documentation, look for 400 Bad Request responses with schema type string
or "Response body: string"
.
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/1.1 400 Bad Request
"Please request fewer than 10000 customers at a time"
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;
}
}
In the API documentation, look for 400 Bad Request responses with schema type BadRequestResponse
showing message
field with propertyErrors
as null or empty.
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/1.1 400 Bad Request
{
"message": "Customer is null or unparseable",
"propertyErrors": null
}
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;
}
}
In the API documentation, look for 400 Bad Request responses with schema type BadRequestResponse
showing message
and populated propertyErrors
array.
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/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"
}
]
}
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;
}
}
In the API documentation, look for 400 Bad Request responses with schema type ValidationResult
or showing isValid
, errors
, and ruleSetsExecuted
fields.
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/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": []
}
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;
}
}
Resource not found errors return empty response body with HTTP 404 status.
HTTP/1.1 404 Not Found
Content-Length: 0
Internal server errors return empty response body with HTTP 500 status.
HTTP/1.1 500 Internal Server Error
Content-Length: 0
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;
}
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.');
}
}