The inventory module serves warehouses and storage locations, stock levels and availability, goods movements of every posting type, reservations, usage decisions, stock loans, batches, and serial numbers. All 51 endpoints sit under /api/v1/inventory. For business meaning see the Inventory module page.
Group facts¶
| Item | Value | Source |
|---|---|---|
| Base path | /api/v1/inventory |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/inventory/module.py:52 |
| Routers | 8 router objects in 3 files; listed in the section Endpoint summary | src/cluster_erp/modules/inventory/module.py:33 |
| Endpoints | 51, counted from the route decorators and add_api_route calls of the routers that register_routes mounts |
src/cluster_erp/modules/inventory/batches/routes.py, src/cluster_erp/modules/inventory/stock/routes.py, src/cluster_erp/modules/inventory/warehouses/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 | 51 of 51 routes declare require_permission |
src/cluster_erp/modules/auth/guards.py:205-240 |
| App entitlement gate | require_app("inventory") on every mounted router |
src/cluster_erp/modules/inventory/module.py:62 |
| 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 |
|---|---|---|---|
warehouses |
warehouse_router (/warehouses), location_router (/locations) |
12 | src/cluster_erp/modules/inventory/warehouses/routes.py:33, src/cluster_erp/modules/inventory/warehouses/routes.py:34 |
stock |
stock_router (/stock), movement_router (/movements), reservation_router (/reservations), usage_decision_router (/usage-decisions) |
29 | src/cluster_erp/modules/inventory/stock/routes.py:47, src/cluster_erp/modules/inventory/stock/routes.py:48, src/cluster_erp/modules/inventory/stock/routes.py:49, src/cluster_erp/modules/inventory/stock/routes.py:50 |
batches |
batch_router (/batches), serial_router (/serial-numbers) |
10 | src/cluster_erp/modules/inventory/batches/routes.py:33, src/cluster_erp/modules/inventory/batches/routes.py:34 |
Warehouses and locations endpoints¶
Two routers: warehouses (with a dashboard and the locations of a warehouse) and storage locations.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/inventory/warehouses/ |
list_warehouses |
warehouse.read |
none | search, is_active, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:66 |
GET |
/api/v1/inventory/warehouses/dashboard |
warehouse_dashboard |
warehouse.read |
none | none | WarehouseDashboardRead |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:99 |
POST |
/api/v1/inventory/warehouses/ |
create_warehouse |
warehouse.create |
WarehouseCreate |
none | WarehouseRead |
201 |
src/cluster_erp/modules/inventory/warehouses/routes.py:108 |
GET |
/api/v1/inventory/warehouses/{warehouse_id} |
get_warehouse |
warehouse.read |
none | none | WarehouseRead |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:119 |
PUT |
/api/v1/inventory/warehouses/{warehouse_id} |
update_warehouse |
warehouse.update |
WarehouseUpdate |
none | WarehouseRead |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:132 |
DELETE |
/api/v1/inventory/warehouses/{warehouse_id} |
delete_warehouse |
warehouse.delete |
none | none | none | 204 |
src/cluster_erp/modules/inventory/warehouses/routes.py:146 |
GET |
/api/v1/inventory/warehouses/{warehouse_id}/locations/ |
list_warehouse_locations |
storage_location.read |
none | search, is_active, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:157 |
GET |
/api/v1/inventory/locations/ |
list_locations |
storage_location.read |
none | warehouse_id, search, is_active, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:198 |
POST |
/api/v1/inventory/locations/ |
create_location |
storage_location.create |
StorageLocationCreate |
none | StorageLocationRead |
201 |
src/cluster_erp/modules/inventory/warehouses/routes.py:233 |
GET |
/api/v1/inventory/locations/{location_id} |
get_location |
storage_location.read |
none | none | StorageLocationRead |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:244 |
PUT |
/api/v1/inventory/locations/{location_id} |
update_location |
storage_location.update |
StorageLocationUpdate |
none | StorageLocationRead |
200 (default) | src/cluster_erp/modules/inventory/warehouses/routes.py:257 |
DELETE |
/api/v1/inventory/locations/{location_id} |
delete_location |
storage_location.delete |
none | none | none | 204 |
src/cluster_erp/modules/inventory/warehouses/routes.py:271 |
Stock, movements, and reservations endpoints¶
Four routers: stock levels and availability with posting routes (goods receipt and issue, transfer, ownership transfer, loans, adjustment, blocked receipt, category transfer, return to vendor, scrap, subcontracting), movements with reversal, reservations, and usage decisions.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/inventory/stock/levels/ |
list_stock_levels |
stock_level.read |
none | product_id, product_model_id, product_version_id, warehouse_id, batch_id, custodian_party_id, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:59 |
GET |
/api/v1/inventory/stock/levels/row/{level_id} |
get_stock_level_row |
stock_level.read |
none | none | StockLevelListRead |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:93 |
GET |
/api/v1/inventory/stock/levels/{product_id} |
get_stock_level |
stock_level.read |
none | warehouse_id, product_model_id, product_version_id |
dict |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:113 |
GET |
/api/v1/inventory/stock/availability/{product_id} |
check_stock_availability |
stock_level.read |
none | product_model_id, product_version_id, qty, warehouse_id |
dict |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:129 |
POST |
/api/v1/inventory/stock/goods-receipt |
post_goods_receipt |
goods_movement.create |
GoodsReceiptCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:156 |
POST |
/api/v1/inventory/stock/goods-issue |
post_goods_issue |
goods_movement.create |
GoodsIssueCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:185 |
POST |
/api/v1/inventory/stock/transfer |
post_transfer |
goods_movement.create |
TransferCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:201 |
POST |
/api/v1/inventory/stock/ownership-transfer |
post_ownership_transfer |
goods_movement.create |
OwnershipTransferCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:217 |
POST |
/api/v1/inventory/stock/loans/borrow |
post_stock_borrow |
goods_movement.create |
StockBorrowCreate |
none | ProjectStockLoanRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:235 |
POST |
/api/v1/inventory/stock/loans/{loan_id}/payback |
post_stock_payback |
goods_movement.create |
StockPaybackCreate |
none | ProjectStockLoanRead |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:251 |
GET |
/api/v1/inventory/stock/loans |
list_stock_loans |
stock_level.read |
none | project_id, status, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:267 |
POST |
/api/v1/inventory/stock/adjustment |
post_adjustment |
goods_movement.create |
AdjustmentCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:290 |
POST |
/api/v1/inventory/stock/gr-blocked-receipt |
post_gr_blocked_receipt |
goods_movement.create |
GrBlockedReceiptCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:306 |
POST |
/api/v1/inventory/stock/stock-category-transfer |
post_stock_category_transfer |
goods_movement.create |
StockCategoryTransferCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:322 |
POST |
/api/v1/inventory/stock/return-to-vendor |
post_return_to_vendor |
goods_movement.create |
ReturnToVendorCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:341 |
POST |
/api/v1/inventory/stock/scrap |
post_scrap |
goods_movement.create |
ScrapPostingCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:357 |
POST |
/api/v1/inventory/stock/subcontract-issue |
post_subcontract_issue |
goods_movement.create |
SubcontractStockCreate |
none | dict |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:598 |
POST |
/api/v1/inventory/stock/subcontract-return |
post_subcontract_return |
goods_movement.create |
SubcontractStockCreate |
none | dict |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:610 |
POST |
/api/v1/inventory/stock/subcontract-consume |
post_subcontract_consume |
goods_movement.create |
SubcontractStockCreate |
none | dict |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:622 |
POST |
/api/v1/inventory/movements/{movement_id}/reverse |
reverse_movement |
goods_movement.create |
MovementReversalCreate |
none | GoodsMovementRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:376 |
GET |
/api/v1/inventory/movements/ |
list_movements |
goods_movement.read |
none | product_id, product_model_id, product_version_id, warehouse_id, batch_id, movement_type, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:400 |
GET |
/api/v1/inventory/movements/{movement_id} |
get_movement |
goods_movement.read |
none | none | GoodsMovementRead |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:434 |
GET |
/api/v1/inventory/reservations/ |
list_reservations |
stock_reservation.read |
none | product_id, warehouse_id, reference_type, reference_id, status, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:451 |
POST |
/api/v1/inventory/reservations/ |
create_reservation |
stock_reservation.create |
StockReservationCreate |
none | StockReservationRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:483 |
POST |
/api/v1/inventory/reservations/{reservation_id}/consume |
consume_reservation |
stock_reservation.create |
none | none | StockReservationRead |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:499 |
POST |
/api/v1/inventory/reservations/{reservation_id}/cancel |
cancel_reservation |
stock_reservation.create |
none | none | StockReservationRead |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:514 |
GET |
/api/v1/inventory/usage-decisions/ |
list_usage_decisions |
usage_decision.read |
none | movement_id, product_id, warehouse_id, decision_code, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:532 |
GET |
/api/v1/inventory/usage-decisions/{decision_id} |
get_usage_decision |
usage_decision.read |
none | none | UsageDecisionRead |
200 (default) | src/cluster_erp/modules/inventory/stock/routes.py:562 |
POST |
/api/v1/inventory/usage-decisions/ |
create_usage_decision |
usage_decision.create |
UsageDecisionCreate |
none | UsageDecisionRead |
201 |
src/cluster_erp/modules/inventory/stock/routes.py:579 |
Batches and serial numbers endpoints¶
Two routers: batches and serial numbers.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/inventory/batches/ |
list_batches |
batch.read |
none | product_id, origin_type, status, search, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/batches/routes.py:49 |
POST |
/api/v1/inventory/batches/ |
create_batch |
batch.create |
BatchCreate |
none | BatchRead |
201 |
src/cluster_erp/modules/inventory/batches/routes.py:87 |
GET |
/api/v1/inventory/batches/{batch_id} |
get_batch |
batch.read |
none | none | BatchRead |
200 (default) | src/cluster_erp/modules/inventory/batches/routes.py:99 |
PUT |
/api/v1/inventory/batches/{batch_id} |
update_batch |
batch.update |
BatchUpdate |
none | BatchRead |
200 (default) | src/cluster_erp/modules/inventory/batches/routes.py:113 |
DELETE |
/api/v1/inventory/batches/{batch_id} |
delete_batch |
batch.delete |
none | none | none | 204 |
src/cluster_erp/modules/inventory/batches/routes.py:128 |
GET |
/api/v1/inventory/serial-numbers/ |
list_serial_numbers |
serial_number.read |
none | product_id, batch_id, status, search, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/inventory/batches/routes.py:143 |
POST |
/api/v1/inventory/serial-numbers/ |
create_serial_number |
serial_number.create |
SerialNumberCreate |
none | SerialNumberRead |
201 |
src/cluster_erp/modules/inventory/batches/routes.py:173 |
GET |
/api/v1/inventory/serial-numbers/{serial_id} |
get_serial_number |
serial_number.read |
none | none | SerialNumberRead |
200 (default) | src/cluster_erp/modules/inventory/batches/routes.py:185 |
PUT |
/api/v1/inventory/serial-numbers/{serial_id} |
update_serial_number |
serial_number.update |
SerialNumberUpdate |
none | SerialNumberRead |
200 (default) | src/cluster_erp/modules/inventory/batches/routes.py:199 |
DELETE |
/api/v1/inventory/serial-numbers/{serial_id} |
delete_serial_number |
serial_number.delete |
none | none | none | 204 |
src/cluster_erp/modules/inventory/batches/routes.py:214 |
Error codes¶
The table lists the error codes that code under src/cluster_erp/modules/inventory/ 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 |
|---|---|---|---|
INVENTORY_BATCH_EXPIRED |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:2166 |
INVENTORY_BATCH_REQUIRED |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:2143 |
INVENTORY_DECISION_QTY_EXCEEDS |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1527 |
INVENTORY_INSUFFICIENT_STOCK |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:853 |
INVENTORY_INVALID_CATEGORY_TRANSFER |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1223 |
INVENTORY_INVALID_DECISION_CODE |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1502 |
INVENTORY_INVALID_MOVEMENT |
409, 422 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:553 |
INVENTORY_INVALID_MOVEMENT_TYPE |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:332 |
INVENTORY_INVALID_ORIGIN_TYPE |
400 |
batches |
src/cluster_erp/modules/inventory/batches/services.py:189 |
INVENTORY_INVALID_STOCK_CATEGORY |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1211 |
INVENTORY_MOVEMENT_ALREADY_REVERSED |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1742 |
INVENTORY_MOVEMENT_NOT_DECIDABLE |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1515 |
INVENTORY_PO_RECEIPT_REQUIRES_PO_FLOW |
409 |
stock |
src/cluster_erp/modules/inventory/stock/routes.py:174 |
INVENTORY_PRODUCT_NOT_STOCKABLE |
400 |
batches, stock |
src/cluster_erp/modules/inventory/batches/services.py:210 |
INVENTORY_PROJECT_TRANSFER_DISABLED |
403 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:563 |
INVENTORY_RESERVATION_ALREADY_CANCELLED |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1998 |
INVENTORY_RESERVATION_ALREADY_CONSUMED |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1992 |
INVENTORY_RESERVATION_NOT_FOUND |
404 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:1985 |
INVENTORY_SERIAL_ALREADY_USED |
409 |
batches, stock |
src/cluster_erp/modules/inventory/batches/services.py:349 |
INVENTORY_SERIAL_REQUIRED |
400 |
stock |
src/cluster_erp/modules/inventory/stock/services.py:2149 |
RESOURCE_CONFLICT |
409 |
batches, warehouses |
src/cluster_erp/modules/inventory/batches/services.py:228 |
RESOURCE_NOT_FOUND |
404 |
batches, stock, warehouses |
src/cluster_erp/modules/inventory/batches/routes.py:109 |
Events emitted¶
The table lists the contract classes that code under src/cluster_erp/modules/inventory/ 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 |
|---|---|---|
BatchCreated |
1 | src/cluster_erp/modules/inventory/batches/services.py:134 |
GoodsIssued |
2 | src/cluster_erp/modules/inventory/stock/services.py:527 |
GoodsMovementReversed |
1 | src/cluster_erp/modules/inventory/stock/services.py:1879 |
GoodsReceived |
1 | src/cluster_erp/modules/inventory/stock/services.py:431 |
GrBlockedReceived |
1 | src/cluster_erp/modules/inventory/stock/services.py:1181 |
ReturnedToVendor |
1 | src/cluster_erp/modules/inventory/stock/services.py:1387 |
ScrapPosted |
1 | src/cluster_erp/modules/inventory/stock/services.py:1482 |
StockAdjusted |
1 | src/cluster_erp/modules/inventory/stock/services.py:1101 |
StockCategoryTransferred |
2 | src/cluster_erp/modules/inventory/stock/services.py:875 |
StockReservationCancelled |
1 | src/cluster_erp/modules/inventory/stock/services.py:2073 |
StockReservationConsumed |
1 | src/cluster_erp/modules/inventory/stock/services.py:2020 |
StockReserved |
1 | src/cluster_erp/modules/inventory/stock/services.py:1957 |
StockTransferred |
1 | src/cluster_erp/modules/inventory/stock/services.py:1039 |
UsageDecisionRecorded |
1 | src/cluster_erp/modules/inventory/stock/services.py:1653 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.