The calendars module serves working calendars, shifts, calendar templates, calendar exceptions, holiday import, a calendar preview, the group calendar, and owner calendars. All 23 endpoints sit under /api/v1/calendars. For business meaning see the Calendars module page.
Group facts¶
| Item | Value | Source |
|---|---|---|
| Base path | /api/v1/calendars |
src/cluster_erp/core/module_base.py:37-38, src/cluster_erp/modules/calendars/module.py:39 |
| Routers | 1 router objects in 1 files; listed in the section Endpoint summary | src/cluster_erp/modules/calendars/module.py:33 |
| Endpoints | 23, counted from the route decorators and add_api_route calls of the routers that register_routes mounts |
src/cluster_erp/modules/calendars/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 | 23 of 23 routes declare require_permission |
src/cluster_erp/modules/auth/guards.py:205-240 |
| App entitlement gate | require_app("calendars") on every mounted router |
src/cluster_erp/modules/calendars/module.py:41 |
| 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 |
|---|---|---|---|
routes |
router |
23 | src/cluster_erp/modules/calendars/routes.py:44 |
Working calendars endpoints¶
One router with no prefix of its own: shifts, templates, calendars, exceptions per calendar, holiday countries and import, a preview, the group calendar, and for-owner to ensure an owner's calendar.
| Method | Path | Handler | Permission | Request | Query | Response | Status | Source |
|---|---|---|---|---|---|---|---|---|
GET |
/api/v1/calendars/group |
get_group_calendar |
work_calendar.read |
none | none | CalendarRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:79 |
GET |
/api/v1/calendars/holiday-countries |
list_holiday_countries |
work_calendar.read |
none | none | dict |
200 (default) | src/cluster_erp/modules/calendars/routes.py:95 |
POST |
/api/v1/calendars/calendars/{calendar_id}/import-holidays |
import_holidays |
work_calendar.create |
ImportHolidaysRequest |
none | dict |
200 (default) | src/cluster_erp/modules/calendars/routes.py:103 |
GET |
/api/v1/calendars/shifts |
list_shifts |
work_calendar.read |
none | none | list[ShiftRead] |
200 (default) | src/cluster_erp/modules/calendars/routes.py:126 |
POST |
/api/v1/calendars/shifts |
create_shift |
work_calendar.create |
ShiftCreate |
none | ShiftRead |
201 |
src/cluster_erp/modules/calendars/routes.py:134 |
PUT |
/api/v1/calendars/shifts/{shift_id} |
update_shift |
work_calendar.update |
ShiftUpdate |
none | ShiftRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:143 |
DELETE |
/api/v1/calendars/shifts/{shift_id} |
delete_shift |
work_calendar.delete |
none | none | none | 204 |
src/cluster_erp/modules/calendars/routes.py:156 |
GET |
/api/v1/calendars/templates |
list_templates |
work_calendar.read |
none | none | list[TemplateRead] |
200 (default) | src/cluster_erp/modules/calendars/routes.py:171 |
POST |
/api/v1/calendars/templates |
create_template |
work_calendar.create |
TemplateCreate |
none | TemplateRead |
201 |
src/cluster_erp/modules/calendars/routes.py:180 |
GET |
/api/v1/calendars/templates/{template_id} |
get_template |
work_calendar.read |
none | none | TemplateRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:189 |
PUT |
/api/v1/calendars/templates/{template_id} |
update_template |
work_calendar.update |
TemplateUpdate |
none | TemplateRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:201 |
DELETE |
/api/v1/calendars/templates/{template_id} |
delete_template |
work_calendar.delete |
none | none | none | 204 |
src/cluster_erp/modules/calendars/routes.py:218 |
GET |
/api/v1/calendars/calendars/{calendar_id}/exceptions |
list_exceptions |
work_calendar.read |
none | none | list[ExceptionRead] |
200 (default) | src/cluster_erp/modules/calendars/routes.py:236 |
POST |
/api/v1/calendars/calendars/{calendar_id}/exceptions |
create_exception |
work_calendar.create |
ExceptionCreate |
none | ExceptionRead |
201 |
src/cluster_erp/modules/calendars/routes.py:249 |
PUT |
/api/v1/calendars/exceptions/{exception_id} |
update_exception |
work_calendar.update |
ExceptionUpdate |
none | ExceptionRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:268 |
DELETE |
/api/v1/calendars/exceptions/{exception_id} |
delete_exception |
work_calendar.delete |
none | none | none | 204 |
src/cluster_erp/modules/calendars/routes.py:283 |
GET |
/api/v1/calendars/calendars/{calendar_id}/preview |
preview_calendar |
work_calendar.read |
none | date_from, date_to |
PreviewResponse |
200 (default) | src/cluster_erp/modules/calendars/routes.py:296 |
GET |
/api/v1/calendars/calendars |
list_calendars |
work_calendar.read |
none | none | list[CalendarRead] |
200 (default) | src/cluster_erp/modules/calendars/routes.py:336 |
POST |
/api/v1/calendars/calendars |
create_calendar |
work_calendar.create |
CalendarCreate |
none | CalendarRead |
201 |
src/cluster_erp/modules/calendars/routes.py:345 |
POST |
/api/v1/calendars/calendars/for-owner |
ensure_owner_calendar |
work_calendar.create |
EnsureOwnerCalendarRequest |
none | CalendarRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:354 |
GET |
/api/v1/calendars/calendars/{calendar_id} |
get_calendar |
work_calendar.read |
none | none | CalendarRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:373 |
PUT |
/api/v1/calendars/calendars/{calendar_id} |
update_calendar |
work_calendar.update |
CalendarUpdate |
none | CalendarRead |
200 (default) | src/cluster_erp/modules/calendars/routes.py:385 |
DELETE |
/api/v1/calendars/calendars/{calendar_id} |
delete_calendar |
work_calendar.delete |
none | none | none | 204 |
src/cluster_erp/modules/calendars/routes.py:398 |
Error codes¶
The table lists the error codes that code under src/cluster_erp/modules/calendars/ 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 |
|---|---|---|---|
CALENDAR_BASE_CYCLE |
400 |
services |
src/cluster_erp/modules/calendars/services.py:373 |
CALENDAR_INVALID_EXCEPTION |
400 |
services |
src/cluster_erp/modules/calendars/services.py:447 |
CONCURRENCY_VERSION_CONFLICT |
409 |
services |
src/cluster_erp/modules/calendars/services.py:133 |
RESOURCE_NOT_FOUND |
404 |
routes, services |
src/cluster_erp/modules/calendars/routes.py:152 |
Events emitted¶
The table lists the contract classes that code under src/cluster_erp/modules/calendars/ 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 |
|---|---|---|
WorkingCalendarChanged |
1 | src/cluster_erp/modules/calendars/routes.py:58 |
Related pages¶
Verified against commit 92f8051 on 2026-11-13.