Skip to content

Error Handling & Status Codes

Status: Final Version: 1.0 Last Updated: 2026-01-03


Purpose

Define the standard error response model, error codes, HTTP status codes, and error handling patterns for all ESG platform APIs.


Standard Error Response

Schema:

{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error message",
    "details": {}, // Optional: field-level errors or context
    "request_id": "req_abc123",
    "timestamp": "2026-01-03T14:30:00Z"
  }
}

Example - Validation Error:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The given data was invalid.",
    "details": {
      "value": ["The value must be a number.", "The value must be at least 0."],
      "evidence_files": ["At least one evidence file is required."]
    },
    "request_id": "req_550e8400",
    "timestamp": "2026-01-03T14:30:00Z"
  }
}


HTTP Status Codes

Status Code Usage
Success
OK 200 Successful GET, PUT, PATCH
Created 201 Successful POST (resource created)
Accepted 202 Async job queued
No Content 204 Successful DELETE
Client Errors
Bad Request 400 Malformed JSON, invalid parameters
Unauthorized 401 Missing/invalid/expired token
Forbidden 403 Valid token but insufficient permissions
Not Found 404 Resource doesn't exist
Conflict 409 Duplicate submission, state conflict
Unprocessable Entity 422 Validation failed
Too Many Requests 429 Rate limit exceeded
Server Errors
Internal Server Error 500 Unexpected error
Service Unavailable 503 Maintenance mode, queue full

Error Codes

Authentication & Authorization (AUTH_*)

Code HTTP Status Description Action
AUTH_TOKEN_MISSING 401 No Authorization header Provide JWT token
AUTH_TOKEN_INVALID 401 Malformed or tampered token Re-authenticate
AUTH_TOKEN_EXPIRED 401 Token past expiry Use refresh token
AUTH_INSUFFICIENT_PERMISSIONS 403 User role lacks permission Contact admin for access
AUTH_TENANT_MISMATCH 403 Token tenant ≠ X-Tenant-Id header Fix tenant context
AUTH_SCOPE_VIOLATION 403 User lacks site/project access Request access from admin

Validation (VALIDATION_*)

Code HTTP Status Description Details Field
VALIDATION_ERROR 422 Field validation failed Field-level errors
VALIDATION_RULE_FAILED 422 Business rule violated Rule name + details
VALIDATION_EVIDENCE_MISSING 422 Required evidence not attached Required evidence types
VALIDATION_ANOMALY_DETECTED 200 (warning) Outlier detected Warning message

Resource Errors (RESOURCE_*)

Code HTTP Status Description
RESOURCE_NOT_FOUND 404 Entity doesn't exist or user lacks access
RESOURCE_ALREADY_EXISTS 409 Duplicate resource (e.g., submission with same UUID)
RESOURCE_LOCKED 409 Reporting period locked, no edits allowed
RESOURCE_DELETED 410 Resource was soft-deleted

State Errors (STATE_*)

Code HTTP Status Description Example
STATE_TRANSITION_INVALID 409 Illegal state transition Can't approve a rejected submission
STATE_PREREQUISITE_MISSING 409 Prerequisite not met Can't lock period with unreviewed submissions

Rate Limiting (RATE_*)

Code HTTP Status Description Headers
RATE_LIMIT_EXCEEDED 429 Too many requests X-RateLimit-Reset, Retry-After

System Errors (SYSTEM_*)

Code HTTP Status Description
SYSTEM_ERROR 500 Unexpected internal error
SYSTEM_MAINTENANCE 503 Scheduled maintenance
SYSTEM_DATABASE_ERROR 503 Database unavailable
SYSTEM_QUEUE_FULL 503 Background queue at capacity

Laravel Implementation

Exception Handler

// app/Exceptions/Handler.php
class Handler extends ExceptionHandler
{
    public function render($request, Throwable $exception)
    {
        if ($request->expectsJson()) {
            return $this->handleApiException($request, $exception);
        }

        return parent::render($request, $exception);
    }

    protected function handleApiException($request, $exception)
    {
        $status = 500;
        $code = 'SYSTEM_ERROR';
        $message = 'An unexpected error occurred.';
        $details = null;

        if ($exception instanceof ValidationException) {
            $status = 422;
            $code = 'VALIDATION_ERROR';
            $message = 'The given data was invalid.';
            $details = $exception->errors();
        } elseif ($exception instanceof AuthenticationException) {
            $status = 401;
            $code = 'AUTH_TOKEN_INVALID';
            $message = $exception->getMessage();
        } elseif ($exception instanceof AuthorizationException) {
            $status = 403;
            $code = 'AUTH_INSUFFICIENT_PERMISSIONS';
            $message = $exception->getMessage();
        } elseif ($exception instanceof ModelNotFoundException) {
            $status = 404;
            $code = 'RESOURCE_NOT_FOUND';
            $message = 'The requested resource was not found.';
        } elseif ($exception instanceof HttpException) {
            $status = $exception->getStatusCode();
            $code = $this->getErrorCodeForStatus($status);
            $message = $exception->getMessage() ?: Response::$statusTexts[$status];
        }

        return response()->json([
            'error' => [
                'code' => $code,
                'message' => $message,
                'details' => $details,
                'request_id' => $request->attributes->get('request_id'),
                'timestamp' => now()->toIso8601String(),
            ]
        ], $status);
    }
}

Custom Exceptions

// app/Exceptions/StateTransitionException.php
class StateTransitionException extends Exception
{
    public function __construct($from, $to, $reason)
    {
        $message = "Cannot transition from {$from} to {$to}: {$reason}";
        parent::__construct($message);
    }

    public function render($request)
    {
        return response()->json([
            'error' => [
                'code' => 'STATE_TRANSITION_INVALID',
                'message' => $this->getMessage(),
                'request_id' => $request->attributes->get('request_id'),
            ]
        ], 409);
    }
}

Acceptance Criteria

Done When: - [ ] All API endpoints return standard error JSON - [ ] Field-level validation errors included in details - [ ] Request IDs logged and returned in errors - [ ] HTTP status codes match error scenarios - [ ] Rate limit errors include Retry-After header


Cross-References


Change Log

Version Date Author Changes
1.0 2026-01-03 Senior Product Architect Initial error handling specification