The products_and_services module serves unit categories and units with conversion, product categories with a tree, products, product models, product versions, photos, and product vendors. All 55 endpoints sit under /api/v1/products_and_services. For business meaning see the Products and services module page.
Group facts¶
| Item | Value | Source |
|---|---|---|
| Base path | /api/v1/products_and_services |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/products_and_services/module.py:75 |
| Routers | 6 router objects in 6 files; listed in the section Endpoint summary | src/cluster_erp/modules/products_and_services/module.py:52 |
| Endpoints | 55, counted from the route decorators and add_api_route calls of the routers that register_routes mounts |
src/cluster_erp/modules/products_and_services/categories/routes.py, src/cluster_erp/modules/products_and_services/product_models/routes.py, src/cluster_erp/modules/products_and_services/product_versions/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 | 55 of 55 routes declare require_permission |
src/cluster_erp/modules/auth/guards.py:205-240 |
| App entitlement gate | require_app("products_and_services") on every mounted router |
src/cluster_erp/modules/products_and_services/module.py:83 |
| 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 |
|---|---|---|---|
settings |
router (/settings) |
15 | src/cluster_erp/modules/products_and_services/settings/routes.py:29 |
categories |
router (/categories) |
7 | src/cluster_erp/modules/products_and_services/categories/routes.py:22 |
products |
router (/products) |
9 | src/cluster_erp/modules/products_and_services/products/routes.py:32 |
product_models |
router (/models) |
10 | src/cluster_erp/modules/products_and_services/product_models/routes.py:25 |
product_versions |
router (/versions) |
7 | src/cluster_erp/modules/products_and_services/product_versions/routes.py:22 |
vendors |
router (/vendors) |
7 | src/cluster_erp/modules/products_and_services/vendors/routes.py:24 |
Units of measure endpoints¶
Unit categories with a seed of defaults, units, and a unit conversion read.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/products_and_services/settings/unit-categories/ |
list_unit_categories |
unit_category.read |
none | active_only, search |
list[UnitCategoryRead] |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:55 |
POST |
/api/v1/products_and_services/settings/unit-categories/ |
create_unit_category |
unit_category.create |
UnitCategoryCreate |
none | UnitCategoryRead |
201 |
src/cluster_erp/modules/products_and_services/settings/routes.py:73 |
POST |
/api/v1/products_and_services/settings/unit-categories/seed-defaults |
seed_unit_defaults |
unit_category.seed |
none | none | dict |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:84 |
GET |
/api/v1/products_and_services/settings/unit-categories/{category_id} |
get_unit_category |
unit_category.read |
none | none | UnitCategoryRead |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:94 |
PUT |
/api/v1/products_and_services/settings/unit-categories/{category_id} |
update_unit_category |
unit_category.update |
UnitCategoryUpdate |
none | UnitCategoryRead |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:107 |
POST |
/api/v1/products_and_services/settings/unit-categories/bulk-delete |
bulk_delete_unit_categories |
unit_category.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:121 |
DELETE |
/api/v1/products_and_services/settings/unit-categories/{category_id} |
delete_unit_category |
unit_category.delete |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/settings/routes.py:132 |
GET |
/api/v1/products_and_services/settings/units/ |
list_all_units |
unit.read |
none | active_only, category_code |
list[UnitOfMeasureRead] |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:148 |
GET |
/api/v1/products_and_services/settings/unit-categories/{category_id}/units |
list_units_by_category |
unit.read |
none | active_only, search |
list[UnitOfMeasureRead] |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:161 |
POST |
/api/v1/products_and_services/settings/units/ |
create_unit |
unit.create |
UnitOfMeasureCreate |
none | UnitOfMeasureRead |
201 |
src/cluster_erp/modules/products_and_services/settings/routes.py:192 |
GET |
/api/v1/products_and_services/settings/units/convert |
convert_unit |
unit.read |
none | value, from_unit_id, to_unit_id |
dict |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:203 |
GET |
/api/v1/products_and_services/settings/units/{unit_id} |
get_unit |
unit.read |
none | none | UnitOfMeasureRead |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:223 |
PUT |
/api/v1/products_and_services/settings/units/{unit_id} |
update_unit |
unit.update |
UnitOfMeasureUpdate |
none | UnitOfMeasureRead |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:237 |
POST |
/api/v1/products_and_services/settings/units/bulk-delete |
bulk_delete_units |
unit.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/products_and_services/settings/routes.py:251 |
DELETE |
/api/v1/products_and_services/settings/units/{unit_id} |
delete_unit |
unit.delete |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/settings/routes.py:262 |
Categories endpoints¶
Category records and the category tree.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/products_and_services/categories/ |
list_categories |
category.read |
none | active_only |
list[ProductCategoryRead] |
200 (default) | src/cluster_erp/modules/products_and_services/categories/routes.py:25 |
GET |
/api/v1/products_and_services/categories/tree |
category_tree |
category.read |
none | none | list[dict[str, Any]] |
200 (default) | src/cluster_erp/modules/products_and_services/categories/routes.py:36 |
POST |
/api/v1/products_and_services/categories/ |
create_category |
category.create |
ProductCategoryCreate |
none | ProductCategoryRead |
201 |
src/cluster_erp/modules/products_and_services/categories/routes.py:45 |
GET |
/api/v1/products_and_services/categories/{category_id} |
get_category |
category.read |
none | none | ProductCategoryRead |
200 (default) | src/cluster_erp/modules/products_and_services/categories/routes.py:56 |
PUT |
/api/v1/products_and_services/categories/{category_id} |
update_category |
category.update |
ProductCategoryUpdate |
none | ProductCategoryRead |
200 (default) | src/cluster_erp/modules/products_and_services/categories/routes.py:69 |
POST |
/api/v1/products_and_services/categories/bulk-delete |
bulk_delete_categories |
category.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/products_and_services/categories/routes.py:83 |
DELETE |
/api/v1/products_and_services/categories/{category_id} |
delete_category |
category.delete |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/categories/routes.py:94 |
Products endpoints¶
Product records and the product photo.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/products_and_services/products/ |
list_products |
product.read |
none | search, material_type, category_id, is_purchasable, is_sellable, is_manufacturable, is_stockable, status, active_only, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/products_and_services/products/routes.py:41 |
POST |
/api/v1/products_and_services/products/ |
create_product |
product.create |
ProductCreate |
none | ProductRead |
201 |
src/cluster_erp/modules/products_and_services/products/routes.py:91 |
GET |
/api/v1/products_and_services/products/{product_id} |
get_product |
product.read |
none | none | ProductRead |
200 (default) | src/cluster_erp/modules/products_and_services/products/routes.py:110 |
PUT |
/api/v1/products_and_services/products/{product_id} |
update_product |
product.update |
ProductUpdate |
none | ProductRead |
200 (default) | src/cluster_erp/modules/products_and_services/products/routes.py:131 |
POST |
/api/v1/products_and_services/products/bulk-delete |
bulk_delete_products |
product.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/products_and_services/products/routes.py:153 |
DELETE |
/api/v1/products_and_services/products/{product_id} |
delete_product |
product.delete |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/products/routes.py:165 |
GET |
/api/v1/products_and_services/products/{product_id}/photo |
get_product_photo |
product.read |
none | none | Response |
200 (default) | src/cluster_erp/modules/products_and_services/products/routes.py:190 |
POST |
/api/v1/products_and_services/products/{product_id}/photo |
set_product_photo |
product.update |
multipart file |
none | ProductRead |
200 (default) | src/cluster_erp/modules/products_and_services/products/routes.py:216 |
DELETE |
/api/v1/products_and_services/products/{product_id}/photo |
delete_product_photo |
product.update |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/products/routes.py:262 |
Product models endpoints¶
Model records, the models of a product, and the model photo.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/products_and_services/models/ |
list_models_flat |
product_model.read |
none | product_id, active_only |
list[ProductModelListRead] |
200 (default) | src/cluster_erp/modules/products_and_services/product_models/routes.py:28 |
GET |
/api/v1/products_and_services/models/by-product/{product_id} |
list_models |
product_model.read |
none | active_only |
list[ProductModelListRead] |
200 (default) | src/cluster_erp/modules/products_and_services/product_models/routes.py:40 |
POST |
/api/v1/products_and_services/models/ |
create_model |
product_model.create |
ProductModelCreate |
none | ProductModelRead |
201 |
src/cluster_erp/modules/products_and_services/product_models/routes.py:52 |
GET |
/api/v1/products_and_services/models/{model_id} |
get_model |
product_model.read |
none | none | ProductModelRead |
200 (default) | src/cluster_erp/modules/products_and_services/product_models/routes.py:63 |
PUT |
/api/v1/products_and_services/models/{model_id} |
update_model |
product_model.update |
ProductModelUpdate |
none | ProductModelRead |
200 (default) | src/cluster_erp/modules/products_and_services/product_models/routes.py:76 |
POST |
/api/v1/products_and_services/models/bulk-delete |
bulk_delete_models |
product_model.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/products_and_services/product_models/routes.py:90 |
DELETE |
/api/v1/products_and_services/models/{model_id} |
delete_model |
product_model.delete |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/product_models/routes.py:101 |
GET |
/api/v1/products_and_services/models/{model_id}/photo |
get_model_photo |
product_model.read |
none | none | Response |
200 (default) | src/cluster_erp/modules/products_and_services/product_models/routes.py:125 |
POST |
/api/v1/products_and_services/models/{model_id}/photo |
set_model_photo |
product_model.update |
multipart file |
none | ProductModelRead |
200 (default) | src/cluster_erp/modules/products_and_services/product_models/routes.py:150 |
DELETE |
/api/v1/products_and_services/models/{model_id}/photo |
delete_model_photo |
product_model.update |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/product_models/routes.py:195 |
Product versions endpoints¶
Version records and the versions of a model.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/products_and_services/versions/ |
list_versions_flat |
product_version.read |
none | product_model_id, active_only |
list[ProductVersionListRead] |
200 (default) | src/cluster_erp/modules/products_and_services/product_versions/routes.py:25 |
GET |
/api/v1/products_and_services/versions/by-model/{model_id} |
list_versions |
product_version.read |
none | active_only |
list[ProductVersionListRead] |
200 (default) | src/cluster_erp/modules/products_and_services/product_versions/routes.py:37 |
POST |
/api/v1/products_and_services/versions/ |
create_version |
product_version.create |
ProductVersionCreate |
none | ProductVersionRead |
201 |
src/cluster_erp/modules/products_and_services/product_versions/routes.py:49 |
GET |
/api/v1/products_and_services/versions/{version_id} |
get_version |
product_version.read |
none | none | ProductVersionRead |
200 (default) | src/cluster_erp/modules/products_and_services/product_versions/routes.py:60 |
PUT |
/api/v1/products_and_services/versions/{version_id} |
update_version |
product_version.update |
ProductVersionUpdate |
none | ProductVersionRead |
200 (default) | src/cluster_erp/modules/products_and_services/product_versions/routes.py:73 |
POST |
/api/v1/products_and_services/versions/bulk-delete |
bulk_delete_versions |
product_version.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/products_and_services/product_versions/routes.py:87 |
DELETE |
/api/v1/products_and_services/versions/{version_id} |
delete_version |
product_version.delete |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/product_versions/routes.py:98 |
Product vendors endpoints¶
Vendor records and the vendors of a product.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/products_and_services/vendors/ |
list_vendors |
product_vendor.read |
none | product_id, search, active_only, limit, offset |
dict |
200 (default) | src/cluster_erp/modules/products_and_services/vendors/routes.py:27 |
GET |
/api/v1/products_and_services/vendors/by-product/{product_id} |
list_vendors_by_product |
product_vendor.read |
none | active_only |
list[ProductVendorListRead] |
200 (default) | src/cluster_erp/modules/products_and_services/vendors/routes.py:55 |
POST |
/api/v1/products_and_services/vendors/ |
create_vendor |
product_vendor.create |
ProductVendorCreate |
none | ProductVendorRead |
201 |
src/cluster_erp/modules/products_and_services/vendors/routes.py:71 |
GET |
/api/v1/products_and_services/vendors/{vendor_id} |
get_vendor |
product_vendor.read |
none | none | ProductVendorRead |
200 (default) | src/cluster_erp/modules/products_and_services/vendors/routes.py:83 |
PUT |
/api/v1/products_and_services/vendors/{vendor_id} |
update_vendor |
product_vendor.update |
ProductVendorUpdate |
none | ProductVendorRead |
200 (default) | src/cluster_erp/modules/products_and_services/vendors/routes.py:97 |
DELETE |
/api/v1/products_and_services/vendors/{vendor_id} |
delete_vendor |
product_vendor.delete |
none | none | none | 204 |
src/cluster_erp/modules/products_and_services/vendors/routes.py:112 |
POST |
/api/v1/products_and_services/vendors/bulk-delete |
bulk_delete_vendors |
product_vendor.bulk_delete |
BulkDeleteRequest |
none | BulkDeleteResponse |
200 (default) | src/cluster_erp/modules/products_and_services/vendors/routes.py:124 |
Error codes¶
The table lists the error codes that code under src/cluster_erp/modules/products_and_services/ 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 |
products |
src/cluster_erp/modules/products_and_services/products/services.py:511 |
PRODUCT_CAPABILITY_VIOLATION |
400 |
products |
src/cluster_erp/modules/products_and_services/products/services.py:679 |
PRODUCT_IN_ACTIVE_BOM |
422 |
products |
src/cluster_erp/modules/products_and_services/products/services.py:620 |
PRODUCT_MODEL_DEFAULT_PROTECTED |
409 |
product_models |
src/cluster_erp/modules/products_and_services/product_models/services.py:123 |
PRODUCT_VERSION_DEFAULT_PROTECTED |
409 |
product_versions |
src/cluster_erp/modules/products_and_services/product_versions/services.py:102 |
RESOURCE_CONFLICT |
409 |
products, vendors |
src/cluster_erp/modules/products_and_services/products/services.py:292 |
RESOURCE_NOT_FOUND |
404, 422 |
categories, product_models, product_versions, products, settings, vendors |
src/cluster_erp/modules/products_and_services/categories/routes.py:65 |
UNIT_CONVERSION_FAILED |
400 |
settings |
src/cluster_erp/modules/products_and_services/settings/routes.py:214 |
VALIDATION_ERROR |
400, 422 |
product_models, products |
src/cluster_erp/modules/products_and_services/product_models/routes.py:160 |
Events emitted¶
The table lists the contract classes that code under src/cluster_erp/modules/products_and_services/ 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 |
|---|---|---|
ProductCreated |
1 | src/cluster_erp/modules/products_and_services/products/services.py:416 |
ProductDeleted |
1 | src/cluster_erp/modules/products_and_services/products/services.py:652 |
ProductStatusChanged |
1 | src/cluster_erp/modules/products_and_services/products/services.py:591 |
ProductUpdated |
1 | src/cluster_erp/modules/products_and_services/products/services.py:583 |
ServiceCreated |
1 | src/cluster_erp/modules/products_and_services/products/services.py:408 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.