Error Handling
Moon ERP uses standard HTTP status codes and returns consistent JSON error responses. This page documents every error format you may encounter.
HTTP Status Codes
| Code | Meaning | When It Occurs |
|---|---|---|
200 | OK | Successful read, update, or delete |
201 | Created | Successful resource creation |
204 | No Content | Successful operation with no response body |
401 | Unauthenticated | Missing or invalid bearer token |
403 | Forbidden | Valid token but insufficient permissions |
404 | Not Found | Resource does not exist or has been soft-deleted |
422 | Unprocessable Entity | Validation errors in the request body |
500 | Internal Server Error | Unexpected server-side failure |
Error Response Formats
422 -- Validation Error
Returned when the request body fails validation. The errors object contains field names as keys, each mapping to an array of error messages.
{
"message": "The name field is required. (and 1 more error)",
"errors": {
"name": [
"The name field is required."
],
"email": [
"The email field must be a valid email address."
]
}
}Multiple errors per field are possible:
{
"message": "The password field must be at least 8 characters. (and 1 more error)",
"errors": {
"password": [
"The password field must be at least 8 characters.",
"The password field confirmation does not match."
]
}
}Handling Validation Errors
Iterate over the errors object to display field-level messages in your UI. The top-level message is a summary suitable for a toast notification.
401 -- Unauthenticated
Returned when the request has no Authorization header, the token is missing, or the token has been revoked (e.g., after logout).
{
"message": "Unauthenticated."
}Common Causes
- Missing
Authorization: Bearer {token}header - Token was revoked via the logout endpoint
- Malformed token string
403 -- Forbidden
Returned when the authenticated user does not have permission to perform the requested action.
{
"message": "This action is unauthorized."
}Common Causes
- User's role lacks the required permission
- Attempting to access a resource belonging to a different company
- User account is inactive
404 -- Not Found
Returned when the requested resource does not exist or has been soft-deleted.
{
"message": "Resource not found."
}INFO
Laravel may also return a 404 with a slightly different format for route-model binding failures:
{
"message": "No query results for model [Account] 999."
}500 -- Internal Server Error
Returned when an unexpected error occurs on the server. In production, the response is intentionally vague to avoid leaking sensitive information.
{
"message": "Server Error"
}In development environments, the response may include additional debugging information such as a stack trace. This extra detail is never exposed in production.
Error Handling in Code
curl
response=$(curl -s -w "\n%{http_code}" -X POST https://moon-erp.test/api/auth/login \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"email": "bad@example.com", "password": "wrong"}')
status=$(echo "$response" | tail -1)
body=$(echo "$response" | sed '$d')
if [ "$status" -ne 200 ]; then
echo "Error ($status): $body"
fiDart
import 'dart:convert';
import 'package:http/http.dart' as http;
final response = await http.post(
Uri.parse('https://moon-erp.test/api/auth/login'),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode({
'email': 'bad@example.com',
'password': 'wrong',
}),
);
switch (response.statusCode) {
case 200:
final data = jsonDecode(response.body);
// Success -- use data['token']
break;
case 401:
// Invalid credentials or expired token
print('Unauthenticated: ${response.body}');
break;
case 422:
final errors = jsonDecode(response.body)['errors'] as Map;
// Display field-level validation errors
errors.forEach((field, messages) {
print('$field: ${(messages as List).join(', ')}');
});
break;
case 403:
// Insufficient permissions
print('Forbidden: ${response.body}');
break;
case 404:
// Resource not found
print('Not found: ${response.body}');
break;
default:
// Server error or unexpected status
print('Error ${response.statusCode}: ${response.body}');
}Best Practices
- Always check the HTTP status code before parsing the response body.
- Display
errorsfield messages next to their corresponding form inputs for 422 responses. - On 401 responses, redirect the user to the login screen and clear any stored tokens.
- Log 500 errors on the client for debugging, but show a generic message to the user.
- Include the
Accept: application/jsonheader on every request to ensure Laravel returns JSON error responses instead of HTML.