This page states the conventions that the Cluster ERP routes share: methods and status codes, list pagination, search and filters, identifiers, money values, deactivation instead of deletion, bulk operations, action endpoints, and optimistic concurrency. Counts on this page cover the 1,364 routes in src/cluster_erp/modules/ and src/cluster_erp/server/routes/, from the same scan as the module pages. The seven routes declared in src/cluster_erp/server/app.py are on System endpoints.
Methods¶
| Method | Routes | Use | Example |
|---|---|---|---|
GET |
510 | list, read, and read models | src/cluster_erp/modules/inventory/warehouses/routes.py:66 |
POST |
531 | create, actions, bulk operations, and computations | src/cluster_erp/modules/inventory/warehouses/routes.py:108 |
PUT |
158 | update a record | src/cluster_erp/modules/inventory/warehouses/routes.py:132 |
PATCH |
8 | partial update in sales_orders and in tenant fields, and one MRP item action |
src/cluster_erp/modules/sales_orders/orders/routes.py (update_order) |
DELETE |
157 | deactivate a record or remove a dependent row | src/cluster_erp/modules/inventory/warehouses/routes.py:146 |
Collection routes end with a slash in 263 route paths, for example /api/v1/inventory/warehouses/ (src/cluster_erp/modules/inventory/warehouses/routes.py:66). The module pages show the exact path of every route.
Status codes¶
| Status | Routes that declare it | Meaning in this API | Example |
|---|---|---|---|
201 |
201 | a record was created | src/cluster_erp/modules/inventory/warehouses/routes.py:108 |
202 |
5 | work was accepted and continues, for example a signup or a background MRP run | src/cluster_erp/server/routes/signup.py:146 |
204 |
145 | success without a body, mostly deactivation | src/cluster_erp/modules/inventory/warehouses/routes.py:146 |
| not declared | 1,013 | the framework default for a successful response; the module pages show it as 200 (default) |
see the FastAPI reference |
Some handlers return their own Response object and set the status there, for example sign-out (src/cluster_erp/modules/auth/login/routes.py:373-432). Error statuses are listed on Errors.
Pagination¶
List routes take limit and offset query parameters (111 routes take limit, 105 take offset). Each route sets its own bounds in Query(...); for example the warehouse list uses a default of 50, a minimum of 1, and a maximum of 200 (src/cluster_erp/modules/inventory/warehouses/routes.py:66-73).
A paginated list answers with an envelope. From src/cluster_erp/modules/inventory/warehouses/routes.py:84-96:
return {
"items": await redact_extra_items(
session,
[WarehouseListRead.model_validate(w) for w in items],
"warehouse",
"warehouse",
await actor_allowed(session),
),
"total": total,
"total_capped": svc.last_count_capped,
"limit": limit,
"offset": offset,
}
| Field | Meaning | Source |
|---|---|---|
items |
the page of rows | src/cluster_erp/modules/inventory/warehouses/routes.py:85-91 |
total |
the row count, counted up to the cap | src/cluster_erp/core/pagination.py:33-69 |
total_capped |
true when the count stopped at the cap |
src/cluster_erp/core/pagination.py:65-68 |
limit, offset |
the values the request used | src/cluster_erp/modules/inventory/warehouses/routes.py:94-95 |
The cap is the setting list_count_cap; 0 means exact counts (src/cluster_erp/config/settings.py:324, src/cluster_erp/core/pagination.py:27-30). Not every list route uses this envelope: routes with response_model=list[...] return a bare array. The module pages show the response model of each route.
Search and filters¶
| Parameter | Routes | Meaning | Example |
|---|---|---|---|
search |
93 | free-text search over the entity's search columns | src/cluster_erp/modules/inventory/warehouses/routes.py:66-69 |
status |
46 | filter on a status field | module pages |
active_only |
35 | only active rows | module pages |
is_active |
6 | filter on the is_active flag |
src/cluster_erp/modules/inventory/warehouses/routes.py:70 |
include_inactive |
6 | include deactivated rows | module pages |
extra.<key> |
lists that read tenant fields | filter on a tenant-defined field value | src/cluster_erp/core/tenant_fields.py:2292-2303 |
No route takes a parameter named for sorting: a scan of all query parameter names found no name that contains sort or order_by. The order of a list is fixed by its service. The search engine is explained in Search, pagination, and bulk operations.
Identifiers¶
Path identifiers such as {warehouse_id} are UUIDs. Every model that derives from BaseModel gets its primary key from new_ulid, so ids are ULID values stored as UUIDs (src/cluster_erp/core/models.py:20-24). The id scheme is explained in Base model and ids. A few routes take a code or a key instead, for example {provider} on mail OAuth apps and {key} on the operator console (module pages and System endpoints).
Money values¶
The project's written engineering rules require Decimal for money, Numeric in the database, and strings in JSON. Module schemas declare 774 fields or parameters with a Decimal annotation across 55 schemas.py files (count of lines matching : Decimal in src/cluster_erp/modules/**/schemas.py). Some read schemas declare an explicit string serialiser, for example ExchangeRateRead (src/cluster_erp/core/currencies.py:186-190). The generic record routes turn a Decimal into a string themselves (src/cluster_erp/modules/records/lifecycle_routes.py:47-57).
Whether every route with response_model=dict sends its Decimal values as strings is not determined from code; this page did not audit each of the 323 routes that declare response_model=dict. Clients send money as JSON strings, which a Decimal field accepts. The rule is explained in Money and decimal.
Deactivation instead of deletion¶
A DELETE on a record route deactivates the record: the service sets deleted_at and the route answers 204 (src/cluster_erp/modules/inventory/warehouses/services.py:137-143, src/cluster_erp/modules/inventory/warehouses/routes.py:146-154). Lists exclude rows whose deleted_at is set (src/cluster_erp/modules/inventory/warehouses/services.py:47). A client may send the reason and a note in the X-Deact-Reason and X-Deact-Note headers; the change history stamps them on the row (src/cluster_erp/server/dependencies.py:63-72).
Deactivated rows are read and reactivated through the generic record routes: GET /api/v1/records/{module}/{entity}/inactive, GET /api/v1/records/{module}/{entity}/labels, and POST /api/v1/records/{module}/{entity}/{record_id}/activate (src/cluster_erp/modules/records/lifecycle_routes.py:113-240). The rule is explained in Soft delete and deactivation.
Bulk operations¶
72 routes end with /bulk-delete. They take BulkDeleteRequest with ids and answer BulkDeleteResponse with deleted_count (src/cluster_erp/core/bulk_operations.py:12-17). The helper bulk_soft_delete loads each row through the ORM and sets deleted_at, so every row reaches the change history (src/cluster_erp/core/bulk_operations.py:20-57).
Illustrative request built from BulkDeleteRequest (src/cluster_erp/core/bulk_operations.py:12-13):
POST /api/v1/bom/boms/bulk-delete HTTP/1.1
Host: erp.example.com
Authorization: Bearer <access_token>
Content-Type: application/json
{
"ids": ["<bom_id>", "<bom_id>"]
}
The path above is an illustration of the shape only; the module pages list which entities have a bulk route.
Action endpoints¶
State changes are separate POST routes, not fields in an update body. Two forms exist:
| Form | Example | Source |
|---|---|---|
| a verb after the record path | POST /api/v1/costing/analyses/{analysis_id}/approve |
src/cluster_erp/modules/costing/costing_analyses/routes.py (approve_analysis) |
| a generic action segment | POST /api/v1/fleet/assets/{asset_id}/actions/{action} (43 routes contain /actions/) |
src/cluster_erp/modules/fleet/assets/routes.py (run_asset_action) |
Optimistic concurrency¶
Every model has a version column (src/cluster_erp/core/models.py:73). Update schemas of several entities carry a version field; the service calls check_version, which raises 409 with CONCURRENCY_VERSION_CONFLICT and details.expected_version and details.current_version when the values differ (src/cluster_erp/core/errors.py:411-430). The scan found 60 raise sites of this code. A request without version skips the check (src/cluster_erp/core/errors.py:428-430).
Tenant field values¶
Read schemas that support tenant-defined fields carry them in extra; the route removes the values the caller may not see before it answers, through redact_extra and redact_extra_items (src/cluster_erp/core/tenant_fields.py:2478, src/cluster_erp/modules/inventory/warehouses/routes.py:53-63). The mechanism is explained in Tenant fields.
Terms introduced¶
| Term | Code identifier | Definition | Turkish term | Source |
|---|---|---|---|---|
| list envelope | items, total, total_capped, limit, offset |
The object a paginated list route returns. | liste zarfı | src/cluster_erp/modules/inventory/warehouses/routes.py:84-96 |
| count cap | list_count_cap |
The number of rows after which a list count stops and reports total_capped. |
sayım tavanı | src/cluster_erp/config/settings.py:324 |
| bulk deactivation | BulkDeleteRequest |
A POST .../bulk-delete call that deactivates a list of ids. |
toplu kullanımdan kaldırma | src/cluster_erp/core/bulk_operations.py:12-17 |
Related pages¶
- API reference
- API overview
- Errors
- Search, pagination, and bulk operations
- Soft delete and deactivation
- Money and decimal
Verified against commit 92f8051 on 2026-11-13.