Skip to main content

Error Response Format

All error responses from the POS Kasir API follow a consistent structure:
{
  "message": "Human-readable error message",
  "error": {
    // Optional: Additional error details
  },
  "data": null
}

Error Response Fields

FieldTypeDescription
messagestringA human-readable error message describing what went wrong
errorobjectOptional object containing additional error details or context
datanullAlways null for error responses

HTTP Status Codes

The API uses standard HTTP status codes to indicate the success or failure of requests:

2xx Success

CodeStatusDescription
200OKRequest completed successfully
201CreatedResource created successfully

4xx Client Errors

CodeStatusDescriptionCommon Causes
400Bad RequestInvalid request parameters or bodyValidation errors, malformed JSON, missing required fields
401UnauthorizedAuthentication required or failedMissing/invalid token, expired session
403ForbiddenInsufficient permissionsAccessing resource without proper role
404Not FoundResource does not existInvalid ID, deleted resource
409ConflictResource conflictDuplicate entries, invalid state transition

5xx Server Errors

CodeStatusDescription
500Internal Server ErrorUnexpected server error
5xx errors indicate server-side issues. If you encounter persistent 500 errors, contact support.

Common Error Scenarios

Authentication Errors

Missing Authorization Header

Status: 401 Unauthorized
{
  "message": "Authorization header required",
  "error": {},
  "data": null
}
Solution: Include the Authorization: Bearer <token> header in your request.

Invalid or Expired Token

Status: 401 Unauthorized
{
  "message": "Invalid or expired token",
  "error": {},
  "data": null
}
Solution: Refresh your access token using the /auth/refresh endpoint or re-authenticate.

Invalid Credentials

Status: 401 Unauthorized
{
  "message": "Invalid username or password",
  "error": {},
  "data": null
}
Solution: Verify your email and password are correct.

Insufficient Permissions

Status: 403 Forbidden
{
  "message": "Forbidden - higher role assignment attempt",
  "error": {},
  "data": null
}
Solution: Ensure your user role has permission to perform this action.

Validation Errors

Invalid Request Body

Status: 400 Bad Request
{
  "message": "Invalid request body or validation failed",
  "error": {
    "field": "email",
    "reason": "Invalid email format"
  },
  "data": null
}
Common validation issues:
  • Missing required fields
  • Invalid data types
  • String length constraints
  • Invalid email/UUID format
  • Numeric range violations

Invalid Query Parameters

Status: 400 Bad Request
{
  "message": "Invalid query parameters",
  "error": {},
  "data": null
}
Solution: Check parameter names, types, and values match the API specification.

Resource Errors

Resource Not Found

Status: 404 Not Found
{
  "message": "Product not found",
  "error": {},
  "data": null
}
Common causes:
  • Invalid UUID/ID format
  • Resource has been deleted
  • Accessing a resource from another tenant

Resource Already Exists

Status: 409 Conflict
{
  "message": "Category with this name already exists",
  "error": {},
  "data": null
}
Solution: Use a different name or update the existing resource.

Resource In Use

Status: 409 Conflict
{
  "message": "Category cannot be deleted because it is in use",
  "error": {},
  "data": null
}
Solution: Remove dependencies before deleting the resource.

Business Logic Errors

Invalid State Transition

Status: 409 Conflict
{
  "message": "Order cannot be cancelled",
  "error": {},
  "data": null
}
Example: Attempting to cancel an order that’s already paid.

Payment Processing Errors

Status: 400 Bad Request
{
  "message": "Cash received must be greater than or equal to net total",
  "error": {},
  "data": null
}
Solution: Validate payment amounts before submitting.

Stock Availability

Status: 400 Bad Request
{
  "message": "Insufficient stock for product",
  "error": {
    "product_id": "uuid-here",
    "available": 5,
    "requested": 10
  },
  "data": null
}
Solution: Reduce order quantity or restock the product.

Server Errors

Internal Server Error

Status: 500 Internal Server Error
{
  "message": "Internal server error",
  "error": {},
  "data": null
}
When this occurs:
  • Unexpected server-side exceptions
  • Database connection issues
  • Third-party service failures
If you encounter persistent 500 errors, note the timestamp and affected endpoint, then contact support.

Error Handling Best Practices

1. Check HTTP Status Codes

Always check the HTTP status code before parsing the response:
const response = await fetch(endpoint, options);

if (!response.ok) {
  const error = await response.json();
  console.error(`API Error (${response.status}):`, error.message);
  throw new Error(error.message);
}

const data = await response.json();

2. Implement Retry Logic

Retry failed requests with exponential backoff for transient errors:
async function fetchWithRetry(url, options, maxRetries = 3) {
  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 (4xx)
      if (response.status >= 400 && response.status < 500) {
        const error = await response.json();
        throw new Error(error.message);
      }
      
      // Retry server errors (5xx)
      if (i === maxRetries - 1) {
        throw new Error(`Failed after ${maxRetries} retries`);
      }
      
      // Exponential backoff
      await new Promise(resolve => 
        setTimeout(resolve, Math.pow(2, i) * 1000)
      );
    } catch (error) {
      if (i === maxRetries - 1) throw error;
    }
  }
}

3. Handle Token Expiration

Automatically refresh expired tokens:
async function authenticatedRequest(endpoint, options) {
  let response = await fetch(endpoint, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${accessToken}`
    }
  });
  
  // Token expired, refresh and retry
  if (response.status === 401) {
    await refreshAccessToken();
    
    response = await fetch(endpoint, {
      ...options,
      headers: {
        ...options.headers,
        'Authorization': `Bearer ${accessToken}`
      }
    });
  }
  
  return response;
}

4. Validate Before Sending

Validate data client-side before making API requests:
function validateProduct(product) {
  const errors = [];
  
  if (!product.name || product.name.length < 3) {
    errors.push('Name must be at least 3 characters');
  }
  
  if (product.price <= 0) {
    errors.push('Price must be greater than 0');
  }
  
  if (product.stock < 0) {
    errors.push('Stock cannot be negative');
  }
  
  if (errors.length > 0) {
    throw new Error(errors.join(', '));
  }
}

5. Log Errors Properly

Implement comprehensive error logging:
function logError(error, context) {
  console.error('API Error:', {
    timestamp: new Date().toISOString(),
    message: error.message,
    status: error.status,
    endpoint: context.endpoint,
    method: context.method,
    userId: context.userId,
    stackTrace: error.stack
  });
}

Error Handling Examples

JavaScript/TypeScript

class APIError extends Error {
  constructor(
    message: string,
    public status: number,
    public details?: any
  ) {
    super(message);
    this.name = 'APIError';
  }
}

async function handleAPIRequest<T>(
  endpoint: string,
  options: RequestInit
): Promise<T> {
  try {
    const response = await fetch(endpoint, options);
    const data = await response.json();
    
    if (!response.ok) {
      throw new APIError(
        data.message || 'Request failed',
        response.status,
        data.error
      );
    }
    
    return data.data as T;
  } catch (error) {
    if (error instanceof APIError) {
      // Handle known API errors
      switch (error.status) {
        case 401:
          // Redirect to login
          window.location.href = '/login';
          break;
        case 403:
          // Show permission error
          showNotification('You do not have permission', 'error');
          break;
        case 404:
          // Show not found error
          showNotification('Resource not found', 'error');
          break;
        case 409:
          // Show conflict error
          showNotification(error.message, 'warning');
          break;
        case 500:
          // Show server error
          showNotification('Server error, please try again', 'error');
          break;
        default:
          showNotification(error.message, 'error');
      }
      throw error;
    }
    
    // Handle network errors
    showNotification('Network error, check your connection', 'error');
    throw error;
  }
}

// Usage
try {
  const product = await handleAPIRequest(
    'https://api-pos.agprastyo.me/api/v1/products/123',
    { method: 'GET' }
  );
  console.log('Product:', product);
} catch (error) {
  console.error('Failed to fetch product:', error);
}

Python

import requests
from typing import Dict, Any

class APIError(Exception):
    def __init__(self, message: str, status_code: int, details: Dict = None):
        self.message = message
        self.status_code = status_code
        self.details = details or {}
        super().__init__(self.message)

def handle_api_request(
    method: str,
    endpoint: str,
    **kwargs
) -> Dict[str, Any]:
    try:
        response = requests.request(method, endpoint, **kwargs)
        data = response.json()
        
        if not response.ok:
            raise APIError(
                message=data.get('message', 'Request failed'),
                status_code=response.status_code,
                details=data.get('error', {})
            )
        
        return data.get('data')
    
    except APIError as e:
        # Handle known API errors
        if e.status_code == 401:
            # Refresh token or redirect to login
            raise
        elif e.status_code == 403:
            print(f"Permission denied: {e.message}")
            raise
        elif e.status_code == 404:
            print(f"Not found: {e.message}")
            raise
        elif e.status_code == 409:
            print(f"Conflict: {e.message}")
            raise
        elif e.status_code >= 500:
            print(f"Server error: {e.message}")
            raise
        else:
            print(f"API error: {e.message}")
            raise
    
    except requests.exceptions.RequestException as e:
        print(f"Network error: {e}")
        raise

# Usage
try:
    product = handle_api_request(
        'GET',
        'https://api-pos.agprastyo.me/api/v1/products/123',
        headers={'Authorization': f'Bearer {token}'}
    )
    print('Product:', product)
except APIError as e:
    print(f'Failed to fetch product: {e.message} (Status: {e.status_code})')

Debugging Tips

Log all API requests and responses during development:
const originalFetch = window.fetch;
window.fetch = async (...args) => {
  console.log('Request:', args);
  const response = await originalFetch(...args);
  const clone = response.clone();
  const body = await clone.json();
  console.log('Response:', body);
  return response;
};
Test endpoints interactively to understand error responses:Visit: https://api-pos.agprastyo.me/swagger/index.html
Ensure proper headers are sent:
  • Content-Type: application/json
  • Authorization: Bearer <token>
  • Check CORS headers in browser
Use a JSON validator to check request bodies:
echo '{"name": "test"}' | jq .

Support

If you encounter errors not covered in this documentation:
  1. Check the Swagger documentation for endpoint-specific details
  2. Review the GitHub issues for known issues
  3. Open a new issue with:
    • Error message and status code
    • Request details (endpoint, method, body)
    • Expected vs actual behavior
    • Steps to reproduce

Report an Issue

Open a GitHub issue for bugs or API problems

Next Steps

API Overview

Return to API introduction

Authentication

Learn about authentication

Build docs developers (and LLMs) love