Skip to main content

Overview

ServITech uses a standardized error response format across all API endpoints to ensure consistent error handling on the client side. All errors return structured JSON responses with appropriate HTTP status codes and localized error messages.

Response Format

The API uses the ApiResponse class (app/Http/Responses/ApiResponse.php:8) to generate consistent error responses.

Success Response Structure

{
  "status": 200,
  "message": "Operation completed successfully.",
  "data": {
    "user": {
      "id": 1,
      "name": "John Doe",
      "email": "[email protected]"
    }
  }
}

Error Response Structure

{
  "status": 400,
  "message": "Human-readable error description",
  "errors": {
    "field_name": [
      "Specific error message for this field"
    ]
  }
}
When there are no specific field errors, the errors object will be empty: {}

ApiResponse Class

The centralized response handler provides two static methods:

Success Response

// app/Http/Responses/ApiResponse.php:18
public static function success(
    array $data = [], 
    int $status = Response::HTTP_OK, 
    string $message = null
): JsonResponse {
    if ($message === null) {
        $message = __('http-statuses.200');
    }
    return response()->json([
        'status' => $status,
        'message' => $message,
        'data' => empty($data) ? (object)[] : $data,
    ], $status);
}
Usage:
return ApiResponse::success(
    message: __('messages.user.logged_in'),
    data: ['user' => UserResource::make($user)]
);

Error Response

// app/Http/Responses/ApiResponse.php:37
public static function error(
    string $message = 'Error', 
    array $errors = [], 
    int $status = Response::HTTP_BAD_REQUEST
): JsonResponse {
    return response()->json([
        'status' => $status,
        'message' => $message,
        'errors' => empty($errors) ? (object)[] : $errors,
    ], $status);
}
Usage:
return ApiResponse::error(
    status: Response::HTTP_NOT_FOUND,
    message: __('messages.common.not_found', ['item' => 'Article']),
    errors: ['id' => 'Article with this ID does not exist']
);

HTTP Status Codes

The API uses standard HTTP status codes from Symfony\Component\HttpFoundation\Response:

Success Codes (2xx)

CodeConstantUsage
200HTTP_OKSuccessful GET, PUT, DELETE operations
201HTTP_CREATEDSuccessful POST operations creating new resources

Client Error Codes (4xx)

CodeConstantUsage
400HTTP_BAD_REQUESTGeneral client error, invalid data
401HTTP_UNAUTHORIZEDMissing or invalid authentication token
403HTTP_FORBIDDENInsufficient permissions (wrong role)
404HTTP_NOT_FOUNDResource not found
422HTTP_UNPROCESSABLE_ENTITYValidation errors

Server Error Codes (5xx)

CodeConstantUsage
500HTTP_INTERNAL_SERVER_ERRORUnexpected server errors

Common Error Scenarios

1. Authentication Errors (401)

Missing Token

GET /api/user/profile
{
  "status": 401,
  "message": "Unauthenticated.",
  "errors": {}
}

Invalid Credentials

POST /api/auth/login
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "wrongpassword"
}
{
  "status": 401,
  "message": "These credentials do not match our records.",
  "errors": {
    "password": "These credentials do not match our records."
  }
}
Implementation:
// app/Http/Controllers/Auth/AuthController.php:94
return ApiResponse::error(
    status: Response::HTTP_UNAUTHORIZED,
    message: __('auth.password'),
    errors: ['password' => __('auth.password')]
);

User Not Found

{
  "status": 400,
  "message": "We can't find a user with that email address.",
  "errors": {
    "email": "We can't find a user with that email address."
  }
}
Implementation:
// app/Http/Controllers/Auth/AuthController.php:83
return ApiResponse::error(
    status: Response::HTTP_BAD_REQUEST,
    message: __('passwords.user'),
    errors: ['email' => __('passwords.user')]
);

2. Authorization Errors (403)

DELETE /api/category/tech
Authorization: Bearer {user_token}
{
  "status": 403,
  "message": "This action is unauthorized.",
  "errors": {}
}
This error occurs when a non-admin user attempts to access admin-only routes like /category/* or /repair-request/*.

3. Validation Errors (422)

POST /api/auth/register
Content-Type: application/json

{
  "email": "invalid-email",
  "password": "123"
}
{
  "status": 422,
  "message": "The email field must be a valid email address. (and 1 more error)",
  "errors": {
    "email": [
      "The email field must be a valid email address."
    ],
    "password": [
      "The password field must be at least 8 characters."
    ]
  }
}
Validation errors are automatically handled by Laravel’s FormRequest classes and return 422 status codes with detailed field-level errors.

4. Resource Not Found (404)

GET /api/articles/id/99999
{
  "status": 404,
  "message": "Article not found.",
  "errors": {}
}

5. Already Logged Out (401)

POST /api/auth/logout
Authorization: Bearer {expired_or_invalid_token}
{
  "status": 401,
  "message": "User is already logged out.",
  "errors": {}
}
Implementation:
// app/Http/Controllers/Auth/AuthController.php:267
if (!auth()->check()) {
    return ApiResponse::error(
        message: __('messages.user.already_logged_out'),
        status: Response::HTTP_UNAUTHORIZED
    );
}

6. Password Reset Failures (500)

POST /api/auth/reset-password
Content-Type: application/json

{
  "email": "[email protected]"
}
{
  "status": 500,
  "message": "We were unable to send the password reset link.",
  "errors": {}
}
Implementation:
// app/Http/Controllers/Auth/AuthController.php:180
$sent = $status === Password::RESET_LINK_SENT;
return $sent
    ? ApiResponse::success(status: Response::HTTP_OK, message: __('passwords.sent'))
    : ApiResponse::error(status: Response::HTTP_INTERNAL_SERVER_ERROR, message: __('passwords.not_sent'));

Error Handling Flow

Localized Error Messages

All error messages are localized based on the Accept-Language header.

English Error

POST /api/auth/login
Accept-Language: en
Content-Type: application/json

{"email": "[email protected]", "password": "wrong"}
{
  "status": 400,
  "message": "We can't find a user with that email address.",
  "errors": {
    "email": "We can't find a user with that email address."
  }
}

Spanish Error

POST /api/auth/login
Accept-Language: es
Content-Type: application/json

{"email": "[email protected]", "password": "wrong"}
{
  "status": 400,
  "message": "No podemos encontrar un usuario con esa dirección de correo electrónico.",
  "errors": {
    "email": "No podemos encontrar un usuario con esa dirección de correo electrónico."
  }
}
See Localization for more details on multi-language support.

Message Response Types

For frontend message display, the MessageResponse class provides type constants:
// app/Http/Responses/MessageResponse.php:7
class MessageResponse
{
    public const TYPE_ERROR = 'error';
    public const TYPE_SUCCESS = 'success';
    public const TYPE_WARNING = 'warning';
    public const TYPE_INFO = 'info';

    public static function create(string $title, string $type): array
    {
        return [
            'title' => $title,
            'type' => $type,
        ];
    }
}
Usage (for HTML views):
// app/Http/Controllers/Auth/AuthController.php:237
$type = match ($status) {
    Password::PASSWORD_RESET => MessageResponse::TYPE_SUCCESS,
    default => MessageResponse::TYPE_ERROR,
};

$message = MessageResponse::create($message, $type);
return view('auth.reset-password', compact('message'));

Error Handling Best Practices

Don’t return 200 OK for error conditions. Use the correct 4xx or 5xx status code.
// Good
return ApiResponse::error(
    status: Response::HTTP_NOT_FOUND,
    message: 'Resource not found'
);

// Bad
return ApiResponse::success(
    message: 'Resource not found',
    data: []
);
Give users enough information to fix the issue without exposing sensitive details.
// Good
'errors' => ['email' => 'User with this email does not exist']

// Bad (too vague)
'errors' => ['error' => 'Something went wrong']

// Bad (too detailed/security risk)
'errors' => ['db' => 'MySQL connection failed: host=192.168.1.1']
Always use translation keys instead of hardcoded strings.
// Good
message: __('messages.common.not_found', ['item' => 'Article'])

// Bad
message: 'Article not found'
Map validation errors to specific fields so clients can highlight problem areas.
'errors' => [
    'email' => ['Email is required', 'Email must be valid'],
    'password' => ['Password must be at least 8 characters']
]
Always return the same structure: {status, message, errors} for all errors.

Error Response Examples by Endpoint

Authentication Endpoints

POST /auth/login

// Success (200)
{
  "status": 200,
  "message": "User logged in successfully.",
  "data": {
    "user": {...},
    "token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
    "expires_in": 3600
  }
}

// Invalid credentials (401)
{
  "status": 401,
  "message": "These credentials do not match our records.",
  "errors": {
    "password": "These credentials do not match our records."
  }
}

// User not found (400)
{
  "status": 400,
  "message": "We can't find a user with that email address.",
  "errors": {
    "email": "We can't find a user with that email address."
  }
}

POST /auth/register

// Success (201)
{
  "status": 201,
  "message": "User registered successfully.",
  "data": {
    "user": {...}
  }
}

// Validation error (422)
{
  "status": 422,
  "message": "The email has already been taken.",
  "errors": {
    "email": ["The email has already been taken."]
  }
}

POST /auth/logout

// Success (200)
{
  "status": 200,
  "message": "User logged out successfully.",
  "data": {}
}

// Already logged out (401)
{
  "status": 401,
  "message": "User is already logged out.",
  "errors": {}
}

Protected Endpoints

GET /user/profile

// Success (200)
{
  "status": 200,
  "message": "User information retrieved successfully.",
  "data": {
    "user": {...}
  }
}

// Unauthenticated (401)
{
  "status": 401,
  "message": "Unauthenticated.",
  "errors": {}
}

PUT /user/profile

// Success (200)
{
  "status": 200,
  "message": "User information updated successfully.",
  "data": {
    "user": {...}
  }
}

// Validation error (422)
{
  "status": 422,
  "message": "The email has already been taken.",
  "errors": {
    "email": ["The email has already been taken."]
  }
}

Admin-Only Endpoints

DELETE /category/tech

// Success (200)
{
  "status": 200,
  "message": "Category deleted successfully.",
  "data": {}
}

// Forbidden - not admin (403)
{
  "status": 403,
  "message": "This action is unauthorized.",
  "errors": {}
}

// Not found (404)
{
  "status": 404,
  "message": "Category not found.",
  "errors": {}
}

Testing Error Responses

When testing your client application, simulate these common error scenarios:
# 401 - Missing token
curl http://localhost:8000/api/user/profile

# 401 - Invalid credentials
curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"wrong"}'

# 403 - Insufficient permissions
curl -X DELETE http://localhost:8000/api/category/tech \
  -H "Authorization: Bearer {user_token}"

# 422 - Validation error
curl -X POST http://localhost:8000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"invalid","password":"123"}'

# 404 - Not found
curl http://localhost:8000/api/articles/id/99999

Next Steps

Authentication

Learn about JWT authentication and token handling

Authorization

Understand role-based access control

Localization

See how error messages are localized

API Reference

Explore all API endpoints and their error responses

Build docs developers (and LLMs) love