Routine Templates API
Routines and Templates
Clone templates, manage items, instantiate on demand, and read fairness
Routine templates turn a recurring checklist into real tasks. These routes cover the template library, item management, manual ("Start now") and scheduled instantiation, and the household fairness aggregate used to spot lopsided chore rotation.
Authentication and Permissions
- All routes on this page require authentication and the
routinesplan feature (seeRequireFeature('routines')). Routines is a separate plan feature fromtask_management(Tasks/Focus) — a plan can grant one without the other, and the System Admin controls both independently in the/subscriptionportal's Feature Matrix. - A template is visible to its owner, to any member of the people group it belongs to, or to anyone (as a read-only system template) if
isSystemTemplateis true. - Only the owner can update, delete, or manage the items of a non-system template they created — cloning a system template creates your own editable copy.
- Editing an item's
defaultAssigneeIdis restricted to the template owner or a member of the template's people group.
Endpoint Reference
| Method | Path | Purpose | Request or query | Auth | Source |
|---|---|---|---|---|---|
POST | /api/routine-templates | Create a routine template. | Body: template fields | JWT or user API key | tasks/routine-templates.controller.ts |
GET | /api/routine-templates | List templates visible to the caller: owned, active system templates, and active templates from their people groups. | None | JWT or user API key | tasks/routine-templates.controller.ts |
GET | /api/routine-templates/fairness | Household fairness aggregate for a people group. | Query: groupId (required), windowDays (optional, default 30, max 365) | JWT or user API key | tasks/routine-templates.controller.ts |
GET | /api/routine-templates/:id | Get one template with its items. | Path: id | JWT or user API key | tasks/routine-templates.controller.ts |
PATCH | /api/routine-templates/:id | Update a template. | Path: id, body: partial template fields | JWT or user API key | tasks/routine-templates.controller.ts |
DELETE | /api/routine-templates/:id | Delete a template. | Path: id | JWT or user API key | tasks/routine-templates.controller.ts |
POST | /api/routine-templates/:id/instantiate | "Start now" — create today's tasks from the template's items immediately, independent of its recurrence schedule. | Path: id | JWT or user API key | tasks/routine-templates.controller.ts |
GET | /api/routine-templates/:id/streak | Completion-streak stats: current streak, longest streak, last fully-completed instantiation date. | Path: id | JWT or user API key | tasks/routine-templates.controller.ts |
POST | /api/routine-templates/:id/skip | Skip today's occurrence without creating tasks (e.g. "we're travelling"). Owner only. | Path: id | JWT or user API key | tasks/routine-templates.controller.ts |
POST | /api/routine-templates/:id/clone | Clone a system template into an owned (optionally group-shared) editable copy. | Path: id, body: groupId (optional) | JWT or user API key | tasks/routine-templates.controller.ts |
POST | /api/routine-templates/:id/items | Add an item to a template. | Path: id, body: item fields | JWT or user API key | tasks/routine-templates.controller.ts |
PATCH | /api/routine-templates/:id/items/reorder | Reorder a template's items. | Path: id, body: itemIds | JWT or user API key | tasks/routine-templates.controller.ts |
PATCH | /api/routine-templates/:id/items/:itemId | Update one item. | Path: id,itemId, body: partial item fields | JWT or user API key | tasks/routine-templates.controller.ts |
DELETE | /api/routine-templates/:id/items/:itemId | Remove one item. | Path: id,itemId | JWT or user API key | tasks/routine-templates.controller.ts |
Request Shapes
Template payload
CreateRoutineTemplateDto
name: required, max 200 charsdescription: optional, max 2000 charscolor: optional 6-digit hex color, default#eab308groupId: optional integer — the caller must already be a member of this people groupisActive: optional boolean, defaulttrue— controls visibility (e.g. hiding a shared template from group members without deleting it)autoInstantiate: optional boolean, defaulttrue— whether the nightly scheduler is allowed to auto-instantiate this template on its recurrence schedule. Whenfalse, the template only ever runs viaPOST /:id/instantiate(manual "Start now") or an automation action; it's otherwise identical. Distinct fromisActive.recurrence: requiredRecurrencePatternDto— the same recurrence shape used for recurring calendar events (type: none|daily|weekly|monthly|yearly,interval,daysOfWeek,endType, etc.)rotationStrategy: optional enumfixed|round_robin|least_recently_done, defaultfixed
UpdateRoutineTemplateDto keeps the same structure but makes all fields optional.
Item payload
CreateRoutineTemplateItemDto
title: required, max 240 charsbody: optional, max 8000 charsbodyFormat: optional, currently onlymarkdowncolor: optional 6-digit hex colorpriority: optional enumhigh|medium|lowdurationMinutes: optional integer,>= 1place: optional, max 255 charsdefaultAssigneeId: optional integer — must be the template owner or a member of the template's people grouporder: optional integer — defaults to the next available position
UpdateRoutineTemplateItemDto keeps the same structure but makes all fields optional.
Reorder payload
ReorderRoutineTemplateItemsDto.itemIds: required array of unique integers, at least one — items are reordered to match array position; unknown ids are silently skipped.
Fairness query
GetFairnessQueryDto
groupId: required positive integer — caller must be a memberwindowDays: optional integer,1..365, default30
Streak response
GET /api/routine-templates/:id/streak returns:
currentStreak: consecutive fully-completed instantiations, most recent first (0 if the last instantiation had any incomplete task)longestStreak: the longest run of fully-completed instantiations anywhere in the template's historylastCompletedDate: the most recentinstanceDate(YYYY-MM-DD) where every item created that day was completed, ornulltotalInstantiations: how many distinctinstanceDates exist in the template's history
"Fully completed" means every task created for that instantiation date has a completedAt — one incomplete item breaks the streak for that date, even if the rest were finished.
Rotation Strategies
| Strategy | Assignee resolution |
|---|---|
fixed | Always the item's defaultAssigneeId. |
round_robin | Cycles through the group's member ids (sorted ascending) in order, advancing past whoever was assigned last time this specific item ran. |
least_recently_done | Assigns whoever in the group has the oldest (or no) assignment history for this specific item. |
Rotation only applies when the template has a groupId and the group has at least two members; otherwise the item's defaultAssigneeId (or the template owner) is used.
Example Calls
Clone a system template into a people group
curl -X POST "$PRIMECAL_API/api/routine-templates/12/clone" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"groupId": 9
}'
Create a weekly rotating routine
curl -X POST "$PRIMECAL_API/api/routine-templates" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Saturday House Cleaning",
"groupId": 9,
"rotationStrategy": "round_robin",
"recurrence": {
"type": "weekly",
"interval": 1,
"daysOfWeek": ["SA"]
}
}'
Start a template right now
curl -X POST "$PRIMECAL_API/api/routine-templates/21/instantiate" \
-H "Authorization: Bearer $TOKEN"
Read a routine's streak
curl "$PRIMECAL_API/api/routine-templates/21/streak" \
-H "Authorization: Bearer $TOKEN"
Example response:
{
"currentStreak": 3,
"longestStreak": 5,
"lastCompletedDate": "2026-07-04",
"totalInstantiations": 12
}
Skip today's occurrence
curl -X POST "$PRIMECAL_API/api/routine-templates/21/skip" \
-H "Authorization: Bearer $TOKEN"
Read the household fairness view
curl "$PRIMECAL_API/api/routine-templates/fairness?groupId=9&windowDays=30" \
-H "Authorization: Bearer $TOKEN"
Example response:
[
{ "userId": 101, "assignedCount": 2, "completedCount": 2 },
{ "userId": 102, "assignedCount": 2, "completedCount": 1 }
]
Response and Behavior Notes
POST /api/routine-templates/:id/instantiatecreates one task per item (inorder), records oneRoutineAssignmentHistoryrow per created task, and updates the template'slastInstantiatedDate— the same idempotency marker the nightly scheduler uses, so a manual "Start now" run and the scheduled run on the same day don't double up.- Instantiation is transactional: either every item's task and history row is created, or none are.
GET /api/routine-templatesreturns templates owned by the caller, active system templates, and active templates from any people group the caller belongs to — combined and ordered by creation time.- Cloning a system template never copies
defaultAssigneeIdvalues forward, since a system template's assignees (if any) belong to no one. GET /api/routine-templates/fairnessrequires membership ingroupIdand aggregatesroutine_assignment_historyrows joined to the linked task'scompletedAt.POST /api/routine-templates/:id/skipstampslastInstantiatedDateto today (the same idempotency markerinstantiateuses) but creates no tasks and does not touch rotation history — the next real instantiation resumes rotation exactly where it left off.GET /api/routine-templates/:id/streakderives its numbers entirely from existingroutine_assignment_history+Task.completedAtdata; it does not add any new tracking table.- The nightly scheduler only considers templates with
isActive: true AND autoInstantiate: true.POST /:id/instantiate(manual) and theinstantiate_routineautomation action both ignoreautoInstantiateentirely — they always work regardless of that flag, since disabling auto-run is meant to require an explicit trigger, not disable the template. - Because automation actions run outside any HTTP request, the
instantiate_routineautomation executor separately re-checks the rule owner'sroutinesentitlement before instantiating (whenENABLE_SUBSCRIPTIONS=true) — a plan without theroutinesfeature can't bypass theRoutineTemplatesControllerguard via an automation rule.
Best Practices
- Use
rotationStrategy: round_robinfor chores where turn-taking matters more than raw fairness, andleast_recently_donewhen the group size or schedule is irregular enough that a fixed cycle would feel arbitrary. - Call
GET /api/routine-templates/fairnessperiodically (or from an MCP-connected agent) rather than trying to infer fairness from task history yourself — the aggregate already accounts for completion, not just assignment. - Prefer
POST /api/routine-templates/:id/instantiateover recreating a template's tasks by hand when a routine needs to run outside its normal schedule. - Prefer
POST /api/routine-templates/:id/skipover deleting or deactivating a template when you just need to skip one occurrence (e.g. a holiday week) — deactivating loses the recurrence config, skip doesn't. - Surface
GET /api/routine-templates/:id/streakin reminders or notifications sparingly — it's most motivating shown right after a completion, not on every page load.