The costing module serves costing analyses with their calculation and approval lifecycle, labour rates, and overhead rates. All 30 endpoints sit under /api/v1/costing. For business meaning see the Costing module page.
Group facts¶
| Item | Value | Source |
|---|---|---|
| Base path | /api/v1/costing |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/costing/module.py:46 |
| Routers | 3 router objects in 3 files; listed in the section Endpoint summary | src/cluster_erp/modules/costing/module.py:32 |
| Endpoints | 30, counted from the route decorators and add_api_route calls of the routers that register_routes mounts |
src/cluster_erp/modules/costing/costing_analyses/routes.py, src/cluster_erp/modules/costing/labor_rates/routes.py, src/cluster_erp/modules/costing/overhead_rates/routes.py |
| Authentication | Bearer header or session cookie, resolved by get_current_user; see Authentication and headers |
src/cluster_erp/modules/auth/guards.py:85-167 |
| Permission guard | 30 of 30 routes declare require_permission |
src/cluster_erp/modules/auth/guards.py:205-240 |
| App entitlement gate | require_app("costing") on every mounted router |
src/cluster_erp/modules/costing/module.py:51 |
| Error format | JSON body {"error": {...}}; see Errors |
src/cluster_erp/server/error_handler.py:33-46 |
Endpoint summary¶
One row per sub-module folder. The router prefix is added to the base path.
| Sub-module | Routers | Endpoints | Router source |
|---|---|---|---|
costing_analyses |
router (/analyses) |
18 | src/cluster_erp/modules/costing/costing_analyses/routes.py:31 |
labor_rates |
router (/labor-rates) |
6 | src/cluster_erp/modules/costing/labor_rates/routes.py:22 |
overhead_rates |
router (/overhead-rates) |
6 | src/cluster_erp/modules/costing/overhead_rates/routes.py:22 |
Costing analyses endpoints¶
Analysis records, calculate and recalculate, final price, submit, approve, reject, return to draft, supersede, breakdown, compare, clone as scenario, and rate snapshots.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/costing/analyses/ |
list_analyses |
costing_analysis.read |
none | status, bom_id, quote_id, is_scenario, search, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:34 |
POST |
/api/v1/costing/analyses/ |
create_analysis |
costing_analysis.create |
CostingAnalysisCreate |
none | CostingAnalysisRead |
201 |
src/cluster_erp/modules/costing/costing_analyses/routes.py:66 |
GET |
/api/v1/costing/analyses/{analysis_id} |
get_analysis |
costing_analysis.read |
none | none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:78 |
PUT |
/api/v1/costing/analyses/{analysis_id} |
update_analysis |
costing_analysis.update |
CostingAnalysisUpdate |
none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:92 |
DELETE |
/api/v1/costing/analyses/{analysis_id} |
delete_analysis |
costing_analysis.delete |
none | none | none | 204 |
src/cluster_erp/modules/costing/costing_analyses/routes.py:105 |
POST |
/api/v1/costing/analyses/bulk-delete |
bulk_delete_analyses |
costing_analysis.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:117 |
POST |
/api/v1/costing/analyses/{analysis_id}/calculate |
calculate_analysis |
costing_analysis.calculate |
none | none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:129 |
POST |
/api/v1/costing/analyses/{analysis_id}/recalculate |
recalculate_analysis |
costing_analysis.calculate |
none | none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:144 |
POST |
/api/v1/costing/analyses/{analysis_id}/set-final-price |
set_final_price |
costing_analysis.update |
SetFinalPrice |
none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:159 |
POST |
/api/v1/costing/analyses/{analysis_id}/submit-for-approval |
submit_for_approval |
costing_analysis.submit |
none | none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:175 |
POST |
/api/v1/costing/analyses/{analysis_id}/approve |
approve_analysis |
costing_analysis.approve |
CostingApproveRequest |
none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:190 |
POST |
/api/v1/costing/analyses/{analysis_id}/reject |
reject_analysis |
costing_analysis.reject |
CostingRejectRequest |
none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:206 |
POST |
/api/v1/costing/analyses/{analysis_id}/return-to-draft |
return_to_draft |
costing_analysis.update |
none | none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:222 |
POST |
/api/v1/costing/analyses/{analysis_id}/supersede |
supersede_analysis |
costing_analysis.supersede |
CostingSupersedeRequest |
none | CostingAnalysisRead |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:237 |
GET |
/api/v1/costing/analyses/{analysis_id}/breakdown |
get_breakdown |
costing_analysis.read |
none | none | dict |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:253 |
POST |
/api/v1/costing/analyses/compare |
compare_analyses |
costing_analysis.read |
CostingCompareRequest |
none | dict |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:264 |
POST |
/api/v1/costing/analyses/{analysis_id}/clone-as-scenario |
clone_as_scenario |
costing_analysis.create |
ScenarioOverrides |
none | CostingAnalysisRead |
201 |
src/cluster_erp/modules/costing/costing_analyses/routes.py:275 |
GET |
/api/v1/costing/analyses/{analysis_id}/rate-snapshots |
get_rate_snapshots |
costing_analysis.read |
none | none | list |
200 (default) | src/cluster_erp/modules/costing/costing_analyses/routes.py:292 |
Labour rates endpoints¶
Labour rate records.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/costing/labor-rates/ |
list_labor_rates |
labor_rate.read |
none | work_center_id, operation_type, rate_type, active_only, search, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/costing/labor_rates/routes.py:25 |
POST |
/api/v1/costing/labor-rates/ |
create_labor_rate |
labor_rate.create |
LaborRateCreate |
none | LaborRateRead |
201 |
src/cluster_erp/modules/costing/labor_rates/routes.py:57 |
GET |
/api/v1/costing/labor-rates/{rate_id} |
get_labor_rate |
labor_rate.read |
none | none | LaborRateRead |
200 (default) | src/cluster_erp/modules/costing/labor_rates/routes.py:69 |
PUT |
/api/v1/costing/labor-rates/{rate_id} |
update_labor_rate |
labor_rate.update |
LaborRateUpdate |
none | LaborRateRead |
200 (default) | src/cluster_erp/modules/costing/labor_rates/routes.py:83 |
POST |
/api/v1/costing/labor-rates/bulk-delete |
bulk_delete_labor_rates |
labor_rate.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/costing/labor_rates/routes.py:98 |
DELETE |
/api/v1/costing/labor-rates/{rate_id} |
delete_labor_rate |
labor_rate.delete |
none | none | none | 204 |
src/cluster_erp/modules/costing/labor_rates/routes.py:110 |
Overhead rates endpoints¶
Overhead rate records.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/costing/overhead-rates/ |
list_overhead_rates |
overhead_rate.read |
none | rate_type, active_only, search, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/costing/overhead_rates/routes.py:25 |
POST |
/api/v1/costing/overhead-rates/ |
create_overhead_rate |
overhead_rate.create |
OverheadRateCreate |
none | OverheadRateRead |
201 |
src/cluster_erp/modules/costing/overhead_rates/routes.py:53 |
GET |
/api/v1/costing/overhead-rates/{rate_id} |
get_overhead_rate |
overhead_rate.read |
none | none | OverheadRateRead |
200 (default) | src/cluster_erp/modules/costing/overhead_rates/routes.py:65 |
PUT |
/api/v1/costing/overhead-rates/{rate_id} |
update_overhead_rate |
overhead_rate.update |
OverheadRateUpdate |
none | OverheadRateRead |
200 (default) | src/cluster_erp/modules/costing/overhead_rates/routes.py:79 |
POST |
/api/v1/costing/overhead-rates/bulk-delete |
bulk_delete_overhead_rates |
overhead_rate.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/costing/overhead_rates/routes.py:94 |
DELETE |
/api/v1/costing/overhead-rates/{rate_id} |
delete_overhead_rate |
overhead_rate.delete |
none | none | none | 204 |
src/cluster_erp/modules/costing/overhead_rates/routes.py:106 |
Error codes¶
The table lists the error codes that code under src/cluster_erp/modules/costing/ raises, found by a syntax-tree scan for AppError(...), the factories in src/cluster_erp/core/errors.py (not_found, conflict, protected, permission_denied, privilege_escalation, check_version), and HTTPException(...). A status of 400 is the AppError default (src/cluster_erp/core/errors.py:23). Guard errors (401, 403), the entitlement codes, and validation errors (422) apply to every route and are listed on Errors.
| Code | HTTP status | Raised in | First site |
|---|---|---|---|
CONCURRENCY_VERSION_CONFLICT |
409 |
costing_analyses, labor_rates, overhead_rates |
src/cluster_erp/modules/costing/costing_analyses/services.py:221 |
COSTING_BOM_NOT_FOUND |
404 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:272 |
COSTING_BOM_NO_ITEMS |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:287 |
COSTING_COMPARE_MAX_ITEMS |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:967 |
COSTING_COMPARE_MIN_ITEMS |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:962 |
COSTING_CURRENCY_MISMATCH |
422 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:304 |
COSTING_FINAL_PRICE_REQUIRED |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:751 |
COSTING_IMMUTABLE |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:120 |
COSTING_INVALID_STATUS |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:113 |
COSTING_NOT_FOUND |
404 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:103 |
COSTING_RATE_INVALID_DATES |
400 |
labor_rates, overhead_rates |
src/cluster_erp/modules/costing/labor_rates/services.py:160 |
COSTING_SCENARIO_SOURCE_INVALID |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:1039 |
COSTING_SUPERSEDE_SAME_BOM |
400 |
costing_analyses |
src/cluster_erp/modules/costing/costing_analyses/services.py:874 |
RESOURCE_NOT_FOUND |
404 |
costing_analyses, labor_rates, overhead_rates |
src/cluster_erp/modules/costing/costing_analyses/routes.py:88 |
Events emitted¶
The table lists the contract classes that code under src/cluster_erp/modules/costing/ passes directly to EventBus.emit, found by a syntax-tree scan. Events built in a variable first are not counted. The contracts are described in Contracts reference.
| Contract | Sites | First site |
|---|---|---|
CostingApproved |
1 | src/cluster_erp/modules/costing/costing_analyses/services.py:786 |
CostingCalculated |
1 | src/cluster_erp/modules/costing/costing_analyses/services.py:674 |
CostingCreated |
1 | src/cluster_erp/modules/costing/costing_analyses/services.py:200 |
CostingRateUpdated |
2 | src/cluster_erp/modules/costing/labor_rates/services.py:218 |
CostingRejected |
1 | src/cluster_erp/modules/costing/costing_analyses/services.py:828 |
CostingSuperseded |
1 | src/cluster_erp/modules/costing/costing_analyses/services.py:886 |
LowMarginAlert |
1 | src/cluster_erp/modules/costing/costing_analyses/services.py:694 |
QuoteCostingCompleted |
1 | src/cluster_erp/modules/costing/costing_analyses/services.py:800 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.