Error Codes and Status Codes
This section provides a comprehensive reference for all HTTP status codes, custom error codes, and error response structures used across the MATIH Enterprise Platform. Every service follows the same error conventions, making it possible to write consistent error handling logic in client applications regardless of which service is being called.
HTTP Status Code Usage
The MATIH Platform uses standard HTTP status codes consistently across all services. The following table documents the precise meaning of each status code within the platform context.
2xx Success Codes
| Code | Reason Phrase | Usage |
|---|---|---|
200 | OK | Successful GET, PUT, PATCH, or DELETE request |
201 | Created | Successful POST request that created a new resource |
202 | Accepted | Request accepted for asynchronous processing (e.g., pipeline trigger, async query, tenant provisioning) |
204 | No Content | Successful DELETE request with no response body |
3xx Redirection Codes
| Code | Reason Phrase | Usage |
|---|---|---|
301 | Moved Permanently | API endpoint has been permanently moved to a new URL |
302 | Found | OAuth2/SAML redirect during SSO authentication flow |
304 | Not Modified | Resource has not changed since the last request (ETag/If-None-Match) |
307 | Temporary Redirect | OAuth2 callback redirect during authentication |
4xx Client Error Codes
| Code | Reason Phrase | Usage |
|---|---|---|
400 | Bad Request | Request body validation failed, malformed JSON, missing required fields |
401 | Unauthorized | Missing, expired, or invalid JWT token |
403 | Forbidden | Valid token but insufficient permissions (RBAC or OPA policy denial) |
404 | Not Found | Resource does not exist or is not accessible to the current tenant |
405 | Method Not Allowed | HTTP method not supported for this endpoint |
409 | Conflict | Resource state conflict (e.g., duplicate slug, concurrent update, already exists) |
410 | Gone | Resource has been permanently deleted |
413 | Payload Too Large | Request body exceeds maximum allowed size |
415 | Unsupported Media Type | Content-Type header is not application/json (or multipart for file uploads) |
422 | Unprocessable Entity | Request is syntactically valid but semantically incorrect (e.g., invalid SQL, invalid pipeline DAG) |
429 | Too Many Requests | Rate limit exceeded; check Retry-After header |
5xx Server Error Codes
| Code | Reason Phrase | Usage |
|---|---|---|
500 | Internal Server Error | Unexpected server-side error; details logged for debugging |
502 | Bad Gateway | Upstream service (LLM provider, database, Kafka) is unreachable |
503 | Service Unavailable | Service is starting up, shutting down, or temporarily overloaded |
504 | Gateway Timeout | Upstream request exceeded the configured timeout |
Standard Error Response Structure
All MATIH services return errors in a consistent JSON structure. This allows client applications to implement a single error-handling layer that works across all services.
Error Response Schema
{
"error": {
"code": "string",
"message": "string",
"details": "string | null",
"field": "string | null",
"timestamp": "string (ISO 8601)",
"requestId": "string",
"path": "string",
"traceId": "string | null"
}
}| Field | Type | Description |
|---|---|---|
code | string | Platform-specific error code (e.g., MATIH-IAM-1001) |
message | string | Human-readable error message suitable for display |
details | string or null | Additional technical details (included in non-production environments) |
field | string or null | Specific field name that caused a validation error |
timestamp | string | ISO 8601 timestamp of when the error occurred |
requestId | string | Unique request identifier for correlation with logs |
path | string | The API path that produced the error |
traceId | string or null | OpenTelemetry trace ID for distributed tracing |
Validation Error Response (Multiple Fields)
When a request fails validation on multiple fields, the response includes an errors array:
{
"error": {
"code": "MATIH-COMMON-1000",
"message": "Validation failed",
"timestamp": "2026-02-12T10:30:00.000Z",
"requestId": "req-abc123",
"path": "/api/v1/tenants",
"errors": [
{
"field": "slug",
"message": "Slug must be lowercase alphanumeric with hyphens",
"rejectedValue": "Acme Corp!"
},
{
"field": "adminEmail",
"message": "Must be a valid email address",
"rejectedValue": "not-an-email"
},
{
"field": "plan",
"message": "Must be one of: FREE, STARTER, PROFESSIONAL, ENTERPRISE",
"rejectedValue": "PLATINUM"
}
]
}
}Custom Error Code Format
MATIH custom error codes follow the pattern:
MATIH-{SERVICE}-{NUMBER}| Component | Description | Example |
|---|---|---|
MATIH | Platform prefix (always present) | MATIH |
{SERVICE} | Service abbreviation (3-6 characters) | IAM, TENANT, AI, ML, QUERY, BI |
{NUMBER} | Four-digit error number | 1001, 2003, 5001 |
Number ranges have semantic meaning:
| Range | Category |
|---|---|
| 1000-1999 | Authentication and authorization errors |
| 2000-2999 | Resource validation and business rule errors |
| 3000-3999 | Data access and persistence errors |
| 4000-4999 | External service and integration errors |
| 5000-5999 | System and infrastructure errors |
| 6000-6999 | Rate limiting, quota, and billing errors |
| 7000-7999 | Configuration and feature flag errors |
Per-Service Error Catalogs
IAM Service Error Codes
| Code | HTTP Status | Message | Description |
|---|---|---|---|
MATIH-IAM-1001 | 401 | Authentication token has expired | JWT access token has passed its expiration time |
MATIH-IAM-1002 | 401 | Invalid authentication token | JWT signature verification failed |
MATIH-IAM-1003 | 401 | Authentication token is malformed | Token does not conform to JWT structure |
MATIH-IAM-1004 | 401 | Missing authentication token | No Authorization header provided |
MATIH-IAM-1005 | 401 | Refresh token has expired | Refresh token TTL exceeded |
MATIH-IAM-1006 | 401 | Refresh token has been revoked | Token was explicitly invalidated |
MATIH-IAM-1007 | 401 | Invalid credentials | Email/password combination does not match |
MATIH-IAM-1008 | 401 | Account is locked | Too many failed login attempts |
MATIH-IAM-1009 | 401 | Account is deactivated | User account has been deactivated by admin |
MATIH-IAM-1010 | 401 | MFA challenge required | MFA is enabled; submit MFA code to proceed |
MATIH-IAM-1011 | 401 | Invalid MFA code | MFA verification code is incorrect |
MATIH-IAM-1012 | 401 | MFA code has expired | TOTP code has expired (30-second window) |
MATIH-IAM-1050 | 403 | Insufficient permissions | User does not have the required role/permission |
MATIH-IAM-1051 | 403 | Tenant access denied | User does not belong to the requested tenant |
MATIH-IAM-1052 | 403 | Resource access denied by policy | OPA policy evaluation returned deny |
MATIH-IAM-2001 | 409 | Email already registered | A user with this email already exists in the tenant |
MATIH-IAM-2002 | 400 | Password does not meet requirements | Password complexity rules not satisfied |
MATIH-IAM-2003 | 400 | Invalid role assignment | Specified role does not exist or cannot be assigned |
MATIH-IAM-2004 | 404 | User not found | No user exists with the specified ID |
MATIH-IAM-2005 | 404 | Role not found | No role exists with the specified ID |
MATIH-IAM-4001 | 502 | OAuth2 provider unreachable | Cannot connect to external identity provider |
MATIH-IAM-4002 | 400 | OAuth2 state mismatch | CSRF protection: state parameter does not match |
MATIH-IAM-4003 | 400 | OAuth2 authorization code expired | Code was not exchanged within the allowed window |
MATIH-IAM-4004 | 502 | SAML assertion validation failed | SAML response signature or certificate is invalid |
Tenant Service Error Codes
| Code | HTTP Status | Message | Description |
|---|---|---|---|
MATIH-TENANT-2001 | 409 | Tenant slug already exists | The requested slug is taken by another tenant |
MATIH-TENANT-2002 | 400 | Invalid tenant slug format | Slug must be 3-63 lowercase alphanumeric with hyphens |
MATIH-TENANT-2003 | 422 | Tenant provisioning in progress | Cannot modify tenant while provisioning is active |
MATIH-TENANT-2004 | 404 | Tenant not found | No tenant with the specified ID or slug |
MATIH-TENANT-2005 | 409 | Tenant already active | Cannot activate a tenant that is already active |
MATIH-TENANT-2006 | 409 | Tenant is suspended | Operation not allowed on suspended tenant |
MATIH-TENANT-2010 | 400 | Invalid connector configuration | Connector config validation failed |
MATIH-TENANT-2011 | 404 | Connector not found | Specified connector does not exist |
MATIH-TENANT-2012 | 422 | Connector test failed | Connection test to data source failed |
MATIH-TENANT-2013 | 409 | Connector name already exists | Duplicate connector name within tenant |
MATIH-TENANT-3001 | 500 | Namespace creation failed | Kubernetes namespace could not be created |
MATIH-TENANT-3002 | 500 | Secret provisioning failed | Kubernetes secrets could not be created |
MATIH-TENANT-4001 | 502 | DNS zone creation failed | Azure DNS / Route53 / Cloud DNS API error |
MATIH-TENANT-4002 | 502 | Ingress controller deployment failed | Helm release for NGINX failed |
MATIH-TENANT-4003 | 504 | LoadBalancer IP not assigned | Timed out waiting for external IP assignment |
MATIH-TENANT-6001 | 403 | Tenant user limit exceeded | Cannot create more users; plan limit reached |
MATIH-TENANT-6002 | 403 | Tenant connector limit exceeded | Cannot create more connectors; plan limit reached |
MATIH-TENANT-6003 | 403 | Tenant storage limit exceeded | Data storage quota for this plan has been reached |
AI Service Error Codes
| Code | HTTP Status | Message | Description |
|---|---|---|---|
MATIH-AI-1001 | 401 | Tenant context missing | X-Tenant-ID header is required for AI service calls |
MATIH-AI-2001 | 404 | Conversation not found | No conversation with the specified ID |
MATIH-AI-2002 | 400 | Empty message | Chat message cannot be empty |
MATIH-AI-2003 | 422 | SQL generation failed | Agent could not produce valid SQL from the input |
MATIH-AI-2004 | 422 | SQL validation failed | Generated SQL did not pass safety or syntax checks |
MATIH-AI-2005 | 422 | Intent classification failed | Could not determine the intent of the user query |
MATIH-AI-2006 | 422 | Schema not found for query | No matching schema context for the data source |
MATIH-AI-2007 | 400 | Unsupported SQL dialect | Requested dialect is not supported |
MATIH-AI-2008 | 400 | Visualization not supported for result type | Cannot generate chart for the given data shape |
MATIH-AI-2010 | 404 | Agent not found | Specified agent name does not exist |
MATIH-AI-2011 | 422 | Agent execution failed | Agent raised an exception during execution |
MATIH-AI-2020 | 404 | Session not found | Studio session does not exist or has expired |
MATIH-AI-2021 | 400 | Session has expired | Studio session exceeded timeout |
MATIH-AI-2030 | 422 | DNN architecture invalid | Architecture validation returned errors |
MATIH-AI-2031 | 400 | Unsupported framework | Requested framework is not in the supported list |
MATIH-AI-4001 | 502 | LLM provider unavailable | Cannot reach OpenAI, Anthropic, Azure OpenAI, or vLLM |
MATIH-AI-4002 | 502 | LLM provider rate limited | Upstream LLM API returned 429 |
MATIH-AI-4003 | 504 | LLM provider timeout | LLM request exceeded configured timeout |
MATIH-AI-4004 | 502 | LLM provider returned error | LLM API returned a non-retriable error |
MATIH-AI-4005 | 502 | Query execution failed | Trino/ClickHouse returned an error executing SQL |
MATIH-AI-4006 | 502 | Catalog service unavailable | Cannot reach the catalog service for schema retrieval |
MATIH-AI-4007 | 502 | Vector store unavailable | Qdrant is unreachable for embedding lookups |
MATIH-AI-5001 | 500 | Agent orchestrator internal error | Unexpected error in LangGraph execution |
MATIH-AI-5002 | 500 | State checkpoint failed | Could not persist conversation state |
MATIH-AI-6001 | 429 | Token budget exceeded | Tenant has exceeded the monthly LLM token budget |
MATIH-AI-6002 | 429 | Rate limit exceeded | Too many requests per minute from this user |
MATIH-AI-6003 | 429 | Concurrent request limit | Maximum concurrent streaming connections reached |
Query Engine Error Codes
| Code | HTTP Status | Message | Description |
|---|---|---|---|
MATIH-QUERY-2001 | 400 | SQL syntax error | Query contains invalid SQL syntax |
MATIH-QUERY-2002 | 400 | Unknown catalog | Referenced catalog does not exist |
MATIH-QUERY-2003 | 400 | Unknown schema | Referenced schema does not exist |
MATIH-QUERY-2004 | 400 | Unknown table | Referenced table does not exist |
MATIH-QUERY-2005 | 400 | Unknown column | Referenced column does not exist |
MATIH-QUERY-2006 | 422 | Query exceeds complexity limit | Query plan cost exceeds configured maximum |
MATIH-QUERY-2007 | 422 | Query exceeds row limit | Result set would exceed maximum row count |
MATIH-QUERY-2008 | 400 | DML not allowed | INSERT/UPDATE/DELETE not permitted via this endpoint |
MATIH-QUERY-2009 | 400 | DDL not allowed | CREATE/ALTER/DROP not permitted via this endpoint |
MATIH-QUERY-3001 | 504 | Query execution timeout | Query exceeded the configured execution timeout |
MATIH-QUERY-3002 | 500 | Query execution failed | Trino returned an unexpected execution error |
MATIH-QUERY-4001 | 502 | Trino coordinator unreachable | Cannot connect to the Trino coordinator |
MATIH-QUERY-4002 | 503 | No Trino workers available | All Trino workers are offline |
MATIH-QUERY-5001 | 500 | Result serialization failed | Could not serialize query results to JSON |
ML Service Error Codes
| Code | HTTP Status | Message | Description |
|---|---|---|---|
MATIH-ML-2001 | 404 | Model not found | No model with the specified ID |
MATIH-ML-2002 | 404 | Model version not found | Specified version does not exist |
MATIH-ML-2003 | 409 | Model name already exists | Duplicate model name within tenant |
MATIH-ML-2004 | 422 | Invalid stage transition | Model cannot be moved to the requested stage |
MATIH-ML-2005 | 422 | Training configuration invalid | Training job spec has validation errors |
MATIH-ML-2006 | 404 | Experiment not found | No experiment with the specified ID |
MATIH-ML-2007 | 404 | Deployment not found | Model serving deployment does not exist |
MATIH-ML-2008 | 409 | Deployment already exists | Model is already deployed |
MATIH-ML-2009 | 422 | Prediction input schema mismatch | Input does not match model's expected schema |
MATIH-ML-3001 | 500 | Training job submission failed | Could not submit job to Ray cluster |
MATIH-ML-4001 | 502 | MLflow unreachable | Cannot connect to MLflow tracking server |
MATIH-ML-4002 | 502 | Ray cluster unavailable | Ray head node is not responding |
MATIH-ML-4003 | 502 | Feature store unavailable | Feast online store is unreachable |
MATIH-ML-4004 | 502 | Model artifact download failed | Cannot download model from artifact store |
MATIH-ML-6001 | 429 | GPU quota exceeded | Tenant has no remaining GPU quota |
BI Service Error Codes
| Code | HTTP Status | Message | Description |
|---|---|---|---|
MATIH-BI-2001 | 404 | Dashboard not found | No dashboard with the specified ID |
MATIH-BI-2002 | 409 | Dashboard name already exists | Duplicate dashboard name in workspace |
MATIH-BI-2003 | 404 | Widget not found | Specified widget does not exist on the dashboard |
MATIH-BI-2004 | 400 | Invalid widget configuration | Widget config validation failed |
MATIH-BI-2005 | 422 | Filter configuration invalid | Dashboard filter has an invalid expression |
MATIH-BI-2006 | 400 | Export format not supported | Requested export format is not available |
MATIH-BI-4001 | 502 | Render service unavailable | Server-side rendering service is unreachable |
MATIH-BI-4002 | 504 | Dashboard export timeout | PDF/PNG export exceeded the time limit |
Common Error Codes (All Services)
| Code | HTTP Status | Message | Description |
|---|---|---|---|
MATIH-COMMON-1000 | 400 | Validation failed | One or more request fields failed validation |
MATIH-COMMON-1001 | 400 | Invalid JSON | Request body is not valid JSON |
MATIH-COMMON-1002 | 400 | Missing required field | A required field is missing from the request |
MATIH-COMMON-1003 | 415 | Unsupported content type | Content-Type must be application/json |
MATIH-COMMON-1004 | 413 | Request body too large | Payload exceeds the maximum allowed size |
MATIH-COMMON-3001 | 500 | Database connection failed | Cannot establish connection to PostgreSQL |
MATIH-COMMON-3002 | 500 | Database query failed | SQL execution error in the service database |
MATIH-COMMON-3003 | 500 | Cache connection failed | Cannot connect to Redis |
MATIH-COMMON-4001 | 502 | Kafka connection failed | Cannot connect to Kafka bootstrap servers |
MATIH-COMMON-4002 | 502 | Kafka message publish failed | Failed to publish message to Kafka topic |
MATIH-COMMON-5001 | 500 | Internal server error | Unexpected error; see logs for details |
MATIH-COMMON-5002 | 503 | Service starting up | Service is not yet ready to accept requests |
MATIH-COMMON-5003 | 503 | Service shutting down | Service is in graceful shutdown |
MATIH-COMMON-6001 | 429 | Rate limit exceeded | Too many requests; retry after the specified delay |
Provisioning Error Codes
The tenant provisioning pipeline uses a dedicated ProvisioningErrorCode enum that provides structured error classification for retry logic, alerting, and operator guidance. These codes are embedded in TenantProvisioningException and are used by the provisioning metrics system for failure categorization.
Error Code Categories
| Category | Code Range | Description |
|---|---|---|
| Permission & Access | PERM_001 - PERM_004 | Azure/K8s RBAC, missing/invalid credentials |
| Resource Quota | QUOTA_001 - QUOTA_004 | Azure subscription, K8s namespace, storage, DB connection limits |
| Network & Connectivity | NET_001 - NET_004 | DNS, timeout, TLS, private endpoint errors |
| Resource State | STATE_001 - STATE_004 | Already exists, not found, invalid state, locked |
| Timeout | TIMEOUT_001 - TIMEOUT_004 | Operation, Helm, Terraform, migration timeouts |
| Configuration | CONFIG_001 - CONFIG_004 | Invalid, missing, conflicting config, Helm values |
| External Service | EXT_001 - EXT_004 | API errors, service down, rate limited, Azure throttled |
| Database | DB_001 - DB_008 | Permission, not found, exists, connection, operation, timeout, delete, user creation |
| Kubernetes | K8S_001 - K8S_002 | Client error, pod exec failed |
| Pod Scheduling | SCHED_001 - SCHED_004 | Taint mismatch, node selector, autoscaler, insufficient resources |
| Data | DATA_001 - DATA_003 | Validation, schema migration, integrity |
| Observability | OBS_001 - OBS_002 | Observability setup, metrics setup |
| Infrastructure | INFRA_001 - INFRA_002 | General infrastructure, Terraform apply |
| Internal | INT_001 - INT_004 | Internal error, state inconsistency, rollback failed, step execution |
Each error code includes:
- Retryable flag: Whether automatic retry is appropriate
- Remediation guidance: Operator-facing fix instructions
- Auto-classification:
ProvisioningErrorCode.fromException(cause)maps common exception types to appropriate codes
Domain Exception Classes
Every control-plane service throws domain-specific exceptions instead of generic RuntimeException. This enables precise error classification in metrics, structured error responses, and service-specific error handling.
| Service | Exception Class | Package |
|---|---|---|
| tenant-service | TenantProvisioningException | com.matih.tenant.exception |
| tenant-service | TenantOperationException | com.matih.tenant.exception |
| tenant-service | TenantNotFoundException | com.matih.tenant.exception |
| tenant-service | TenantValidationException | com.matih.tenant.exception |
| tenant-service | TerraformStateException | com.matih.tenant.exception |
| api-gateway | GatewayException | com.matih.gateway.exception |
| infrastructure-service | InfrastructureException | com.matih.infrastructure.exception |
| iam-service | AuthenticationException | com.matih.iam.exception |
| iam-service | BusinessException | com.matih.iam.exception |
| iam-service | TokenException | com.matih.iam.exception |
| config-service | ConfigServiceException | com.matih.config.exception |
| billing-service | BillingException | com.matih.billing.exception |
| audit-service | AuditServiceException | com.matih.audit.exception |
| notification-service | NotificationException | com.matih.notification.exception |
| observability-api | ObservabilityException | com.matih.observability.exception |
All domain exceptions extend RuntimeException and provide at minimum (String message) and (String message, Throwable cause) constructors. TenantProvisioningException additionally supports a Builder pattern with error codes, tenant context, and structured error output.
Error Handling Best Practices
Client-Side Error Handling
When consuming MATIH APIs, implement error handling according to this priority:
try {
const response = await apiClient.post('/api/v1/chat', payload);
return response.data;
} catch (error) {
if (error.response) {
const { status, data } = error.response;
const errorCode = data.error?.code;
switch (status) {
case 401:
// Token expired or invalid - attempt refresh
if (errorCode === 'MATIH-IAM-1001') {
await refreshToken();
return retry(request);
}
// Other 401s - redirect to login
redirectToLogin();
break;
case 403:
// Insufficient permissions - show access denied
showAccessDeniedMessage(data.error.message);
break;
case 429:
// Rate limited - retry after delay
const retryAfter = error.response.headers['retry-after'];
await delay(retryAfter * 1000);
return retry(request);
case 422:
// Business logic error - show to user
showErrorMessage(data.error.message);
break;
case 502:
case 503:
case 504:
// Upstream error - retry with exponential backoff
return retryWithBackoff(request, { maxRetries: 3 });
default:
// Unexpected error - log and show generic message
logError(data.error);
showGenericErrorMessage();
}
}
}Server-Side Error Generation
Services should use the common error factory to generate consistent error responses:
// Java Spring Boot example
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(
ResourceNotFoundException ex, HttpServletRequest request) {
ErrorResponse error = ErrorResponse.builder()
.code("MATIH-TENANT-2004")
.message("Tenant not found")
.details(ex.getMessage())
.timestamp(Instant.now())
.requestId(request.getHeader("X-Request-ID"))
.path(request.getRequestURI())
.traceId(Span.current().getSpanContext().getTraceId())
.build();
return ResponseEntity.status(404).body(error);
}# Python FastAPI example
from fastapi import HTTPException
from datetime import datetime
class MatihError(HTTPException):
def __init__(self, code: str, message: str, status_code: int,
details: str = None):
self.error_code = code
self.error_message = message
super().__init__(
status_code=status_code,
detail={
"error": {
"code": code,
"message": message,
"details": details,
"timestamp": datetime.utcnow().isoformat() + "Z"
}
}
)
# Usage
raise MatihError(
code="MATIH-AI-2003",
message="SQL generation failed",
status_code=422,
details="Could not generate valid SQL for the given natural language query"
)Error Monitoring and Alerting
All errors with HTTP status 500 or above are automatically:
- Logged with full stack trace and request context
- Reported to the observability stack via OpenTelemetry
- Aggregated in Prometheus error rate metrics
- Visible in Grafana error dashboards
- Subject to alerting rules (error rate thresholds trigger PagerDuty/Slack notifications)
The error rate SLO for each service is defined as: fewer than 0.1% of requests should return 5xx status codes, measured over a 30-minute rolling window.