This page lists the error classes and codes of Cluster ERP and how the server turns exceptions into HTTP responses. It covers core/errors.py, core/exceptions.py, and server/error_handler.py. The full list of codes with their causes, as a client sees them, is on API errors.
Scope and source¶
- Defined in:
src/cluster_erp/core/errors.py(AppError, code constants, factories),src/cluster_erp/core/exceptions.py(platform exceptions),src/cluster_erp/server/error_handler.py(install_error_handlers) - Count: 213 module-level string constants of the form
NAME = "NAME"incore/errors.py(counted withgrep -c -E '^[A-Z_0-9]+ = "'), in 23 commented groups; 6 factory functions; 7 exception classes incore/exceptions.py(counted withgrep -c "^class")
The error body¶
Every handled error returns a JSON body built by _build_body (src/cluster_erp/server/error_handler.py:33-46):
{
"error": {
"code": "<CODE>",
"message": "<developer message>",
"trace_id": "<YYMMDD-NNN>",
"details": {}
}
}
details is present only when it is not empty. In debug mode the unhandled-exception handler adds debug_message and exception_type, and the mapped handlers add debug_message (src/cluster_erp/server/error_handler.py:305-308, src/cluster_erp/server/error_handler.py:345-347). debug_message carries at most the first line of the driver message, cut to 300 characters, never the SQL text (src/cluster_erp/server/error_handler.py:99-107).
Trace ids¶
_next_trace_id increments a per-day counter in the table trace_sequence and returns YYMMDD-NNN. If the database is unreachable it returns YYMMDD- with eight digits taken from a monotonic clock (src/cluster_erp/server/error_handler.py:49-70). The table comes from the migration feefe25508db_add_trace_sequence_table.py. Handlers store the trace id and error data on request.state; the activity-log middleware writes them to the error log (src/cluster_erp/server/error_handler.py:73-93, src/cluster_erp/server/middleware.py:638-659).
AppError¶
AppError(code, message, *, status_code=400, details=None) is the exception that services and routes raise (src/cluster_erp/core/errors.py:6-30). The code is machine-readable, the client maps it to a localised text, and the message is an English developer string (src/cluster_erp/core/errors.py:7-16).
Code groups¶
| Group comment | Codes | Lines | Examples |
|---|---|---|---|
| Auth | 12 | src/cluster_erp/core/errors.py:33-48 |
AUTH_TOKEN_EXPIRED, AUTH_PERMISSION_DENIED |
| Resource CRUD | 3 | src/cluster_erp/core/errors.py:50-53 |
RESOURCE_NOT_FOUND, RESOURCE_CONFLICT, RESOURCE_PROTECTED |
| Validation | 7 | src/cluster_erp/core/errors.py:55-75 |
VALIDATION_ERROR, FIELD_VALUE_INVALID |
| Business logic | 5 | src/cluster_erp/core/errors.py:77-82 |
UNIT_CONVERSION_FAILED, APPROVAL_INVALID_STATE |
| Currency | 1 | src/cluster_erp/core/errors.py:84-85 |
TCMB_SYNC_FAILED |
| CRM | 14 | src/cluster_erp/core/errors.py:87-104 |
RFQ_INVALID_STATUS, PRFQ_AWARD_INVALID |
| Quotations | 9 | src/cluster_erp/core/errors.py:106-117 |
QUOTE_INVALID_STATUS, QUOTE_INCOMPLETE |
| Sales Orders | 9 | src/cluster_erp/core/errors.py:119-129 |
ORDER_INVALID_STATUS, PDF_RENDERER_UNAVAILABLE |
| Projects | 8 | src/cluster_erp/core/errors.py:131-139 |
PROJECT_INVALID_STATUS, PROJECT_ACTIVITY_CYCLE |
| BOM | 8 | src/cluster_erp/core/errors.py:141-151 |
BOM_INVALID_STATUS, BOM_MAX_DEPTH_EXCEEDED |
| Design and Engineering | 6 | src/cluster_erp/core/errors.py:153-159 |
DESIGN_INVALID_STATUS, ECN_INVALID_STATUS |
| Products and Materials | 13 | src/cluster_erp/core/errors.py:161-179 |
PRODUCT_NOT_SELLABLE, PRODUCT_VERSION_MODEL_MISMATCH |
| Inventory | 21 | src/cluster_erp/core/errors.py:181-202 |
INVENTORY_INSUFFICIENT_STOCK, INVENTORY_NEGATIVE_STOCK |
| Production | 32 | src/cluster_erp/core/errors.py:204-264 |
see the file |
| Nesting | 14 | src/cluster_erp/core/errors.py:266-280 |
see the file |
| Costing | 17 | src/cluster_erp/core/errors.py:282-299 |
see the file |
| Procurement | 20 | src/cluster_erp/core/errors.py:301-321 |
SUBSTITUTION_GROUP_SINGLE_MEMBER, CRITICAL_MATERIAL_ALREADY_EXISTS |
| Concurrency | 1 | src/cluster_erp/core/errors.py:323-324 |
CONCURRENCY_VERSION_CONFLICT |
| External services | 3 | src/cluster_erp/core/errors.py:326-329 |
SMTP_NOT_CONFIGURED, SMTP_ERROR, FILE_NOT_FOUND_ON_DISK |
| Database | 5 | src/cluster_erp/core/errors.py:331-340 |
DB_INTEGRITY_ERROR, DB_UNIQUE_VIOLATION |
| Serialization | 1 | src/cluster_erp/core/errors.py:342-343 |
SERIALIZATION_ERROR |
| Runtime | 3 | src/cluster_erp/core/errors.py:345-348 |
TYPE_ERROR, VALUE_ERROR, TIMEOUT_ERROR |
| Server | 1 | src/cluster_erp/core/errors.py:350-351 |
INTERNAL_ERROR |
Some modules define further codes in their own files, for example CURRENCY_UNKNOWN (src/cluster_erp/core/currencies.py:27) and UPLOAD_TYPE_REFUSED (src/cluster_erp/core/upload_policy.py:19). They are not counted above.
Factories¶
| Function | Code | HTTP status | Source |
|---|---|---|---|
not_found(resource, resource_id=None) |
RESOURCE_NOT_FOUND |
404 | src/cluster_erp/core/errors.py:357-367 |
conflict(resource, field, value) |
RESOURCE_CONFLICT |
409 | src/cluster_erp/core/errors.py:370-376 |
protected(resource, reason="") |
RESOURCE_PROTECTED |
400 | src/cluster_erp/core/errors.py:379-385 |
permission_denied(permission) |
AUTH_PERMISSION_DENIED |
403 | src/cluster_erp/core/errors.py:388-394 |
privilege_escalation(missing_codes) |
AUTH_PRIVILEGE_ESCALATION |
403 | src/cluster_erp/core/errors.py:397-408 |
version_conflict(resource, expected, actual) and check_version(...) |
CONCURRENCY_VERSION_CONFLICT |
409 | src/cluster_erp/core/errors.py:411-430 |
Exception handlers¶
install_error_handlers(app, debug) registers the handlers below (src/cluster_erp/server/error_handler.py:200-398).
| Exception | Code | HTTP status | Source |
|---|---|---|---|
AppError |
the error's own code | the error's own status | src/cluster_erp/server/error_handler.py:203-224 |
RequestValidationError |
VALIDATION_ERROR, with details.fields listing field and reason |
422 | src/cluster_erp/server/error_handler.py:226-246 |
HTTPException |
HTTP_<status>, or the code of a dictionary detail |
the exception's status | src/cluster_erp/server/error_handler.py:248-270 |
SQLAlchemy DisconnectionError |
DB_CONNECTION_ERROR |
503 | src/cluster_erp/server/error_handler.py:128 |
SQLAlchemy TimeoutError (pool) |
DB_TIMEOUT |
503 | src/cluster_erp/server/error_handler.py:129-131 |
SQLAlchemy IntegrityError |
DB_UNIQUE_VIOLATION with details.constraint when a unique constraint is named, else DB_INTEGRITY_ERROR |
409 | src/cluster_erp/server/error_handler.py:132, src/cluster_erp/server/error_handler.py:352-398 |
SQLAlchemy DataError |
VALIDATION_ERROR |
422 | src/cluster_erp/server/error_handler.py:133-135 |
SQLAlchemy OperationalError |
DB_OPERATIONAL_ERROR |
500 | src/cluster_erp/server/error_handler.py:136 |
decimal.InvalidOperation |
VALIDATION_ERROR |
422 | src/cluster_erp/server/error_handler.py:142-148 |
Pydantic ValidationError |
SERIALIZATION_ERROR |
500 | src/cluster_erp/server/error_handler.py:150-155 |
TimeoutError |
TIMEOUT_ERROR |
504 | src/cluster_erp/server/error_handler.py:161 |
TypeError |
TYPE_ERROR |
500 | src/cluster_erp/server/error_handler.py:162 |
ValueError |
VALUE_ERROR |
500 | src/cluster_erp/server/error_handler.py:163 |
any other Exception |
INTERNAL_ERROR |
500 | src/cluster_erp/server/error_handler.py:272-310 |
A 503 response from the unhandled-exception handler carries Retry-After: 2 (src/cluster_erp/server/error_handler.py:309). Each mapped type gets its own handler so that Starlette's exception middleware handles it instead of the server-error middleware (src/cluster_erp/server/error_handler.py:312-318, src/cluster_erp/server/error_handler.py:394-398).
Platform exceptions¶
These classes in core/exceptions.py are raised by the module registry and the EventBus. They are not mapped to HTTP codes.
| Class | Parent | Raised when | Source |
|---|---|---|---|
ClusterERPError |
Exception |
Base class | src/cluster_erp/core/exceptions.py:4-5 |
ModuleError |
ClusterERPError |
Base class for module errors | src/cluster_erp/core/exceptions.py:8-9 |
ModuleLoadError |
ModuleError |
A module cannot be imported or has no Module class |
src/cluster_erp/core/exceptions.py:12-16 |
ModuleNotRegisteredError |
ModuleError |
registry.get receives an unknown name |
src/cluster_erp/core/exceptions.py:19-22 |
EventBusError |
ClusterERPError |
Base class for bus errors | src/cluster_erp/core/exceptions.py:25-26 |
QueryHandlerNotFoundError |
EventBusError |
A query has no handler | src/cluster_erp/core/exceptions.py:29-32 |
DuplicateQueryHandlerError |
EventBusError |
A second handler is registered for a query type | src/cluster_erp/core/exceptions.py:35-38 |
Constraints and validation¶
- A request body larger than
max_request_body_bytesis refused with HTTP 413 and codeREQUEST_TOO_LARGEby a middleware, before any handler runs (src/cluster_erp/server/app.py:1396-1410). That code is not one of the constants incore/errors.py. - A
RESOURCE_PROTECTEDerror uses HTTP 400, not 403 (src/cluster_erp/core/errors.py:379-385).
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| application error | AppError |
The exception that carries a machine-readable code, a message, a status, and details. | uygulama hatası | src/cluster_erp/core/errors.py:6 |
| trace id | trace_id |
A per-day sequence id that links an error response to its error-log row. | izleme kimliği | src/cluster_erp/server/error_handler.py:58-70 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.