Skip to main content
Was this helpful?

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.

JWT or user API keySystem template cloningRotation strategiesFairness aggregate

Authentication and Permissions

  • All routes on this page require authentication and the routines plan feature (see RequireFeature('routines')). Routines is a separate plan feature from task_management (Tasks/Focus) — a plan can grant one without the other, and the System Admin controls both independently in the /subscription portal'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 isSystemTemplate is 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 defaultAssigneeId is restricted to the template owner or a member of the template's people group.

Endpoint Reference

MethodPathPurposeRequest or queryAuthSource
POST/api/routine-templatesCreate a routine template.Body: template fieldsJWT or user API keytasks/routine-templates.controller.ts
GET/api/routine-templatesList templates visible to the caller: owned, active system templates, and active templates from their people groups.NoneJWT or user API keytasks/routine-templates.controller.ts
GET/api/routine-templates/fairnessHousehold fairness aggregate for a people group.Query: groupId (required), windowDays (optional, default 30, max 365)JWT or user API keytasks/routine-templates.controller.ts
GET/api/routine-templates/:idGet one template with its items.Path: idJWT or user API keytasks/routine-templates.controller.ts
PATCH/api/routine-templates/:idUpdate a template.Path: id, body: partial template fieldsJWT or user API keytasks/routine-templates.controller.ts
DELETE/api/routine-templates/:idDelete a template.Path: idJWT or user API keytasks/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: idJWT or user API keytasks/routine-templates.controller.ts
GET/api/routine-templates/:id/streakCompletion-streak stats: current streak, longest streak, last fully-completed instantiation date.Path: idJWT or user API keytasks/routine-templates.controller.ts
POST/api/routine-templates/:id/skipSkip today's occurrence without creating tasks (e.g. "we're travelling"). Owner only.Path: idJWT or user API keytasks/routine-templates.controller.ts
POST/api/routine-templates/:id/cloneClone a system template into an owned (optionally group-shared) editable copy.Path: id, body: groupId (optional)JWT or user API keytasks/routine-templates.controller.ts
POST/api/routine-templates/:id/itemsAdd an item to a template.Path: id, body: item fieldsJWT or user API keytasks/routine-templates.controller.ts
PATCH/api/routine-templates/:id/items/reorderReorder a template's items.Path: id, body: itemIdsJWT or user API keytasks/routine-templates.controller.ts
PATCH/api/routine-templates/:id/items/:itemIdUpdate one item.Path: id,itemId, body: partial item fieldsJWT or user API keytasks/routine-templates.controller.ts
DELETE/api/routine-templates/:id/items/:itemIdRemove one item.Path: id,itemIdJWT or user API keytasks/routine-templates.controller.ts

Request Shapes

Template payload

CreateRoutineTemplateDto

  • name: required, max 200 chars
  • description: optional, max 2000 chars
  • color: optional 6-digit hex color, default #eab308
  • groupId: optional integer — the caller must already be a member of this people group
  • isActive: optional boolean, default true — controls visibility (e.g. hiding a shared template from group members without deleting it)
  • autoInstantiate: optional boolean, default true — whether the nightly scheduler is allowed to auto-instantiate this template on its recurrence schedule. When false, the template only ever runs via POST /:id/instantiate (manual "Start now") or an automation action; it's otherwise identical. Distinct from isActive.
  • recurrence: required RecurrencePatternDto — the same recurrence shape used for recurring calendar events (type: none|daily|weekly|monthly|yearly, interval, daysOfWeek, endType, etc.)
  • rotationStrategy: optional enum fixed|round_robin|least_recently_done, default fixed

UpdateRoutineTemplateDto keeps the same structure but makes all fields optional.

Item payload

CreateRoutineTemplateItemDto

  • title: required, max 240 chars
  • body: optional, max 8000 chars
  • bodyFormat: optional, currently only markdown
  • color: optional 6-digit hex color
  • priority: optional enum high|medium|low
  • durationMinutes: optional integer, >= 1
  • place: optional, max 255 chars
  • defaultAssigneeId: optional integer — must be the template owner or a member of the template's people group
  • order: 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 member
  • windowDays: optional integer, 1..365, default 30

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 history
  • lastCompletedDate: the most recent instanceDate (YYYY-MM-DD) where every item created that day was completed, or null
  • totalInstantiations: how many distinct instanceDates 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

StrategyAssignee resolution
fixedAlways the item's defaultAssigneeId.
round_robinCycles through the group's member ids (sorted ascending) in order, advancing past whoever was assigned last time this specific item ran.
least_recently_doneAssigns 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/instantiate creates one task per item (in order), records one RoutineAssignmentHistory row per created task, and updates the template's lastInstantiatedDate — 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-templates returns 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 defaultAssigneeId values forward, since a system template's assignees (if any) belong to no one.
  • GET /api/routine-templates/fairness requires membership in groupId and aggregates routine_assignment_history rows joined to the linked task's completedAt.
  • POST /api/routine-templates/:id/skip stamps lastInstantiatedDate to today (the same idempotency marker instantiate uses) 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/streak derives its numbers entirely from existing routine_assignment_history + Task.completedAt data; 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 the instantiate_routine automation action both ignore autoInstantiate entirely — 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_routine automation executor separately re-checks the rule owner's routines entitlement before instantiating (when ENABLE_SUBSCRIPTIONS=true) — a plan without the routines feature can't bypass the RoutineTemplatesController guard via an automation rule.

Best Practices

  • Use rotationStrategy: round_robin for chores where turn-taking matters more than raw fairness, and least_recently_done when the group size or schedule is irregular enough that a fixed cycle would feel arbitrary.
  • Call GET /api/routine-templates/fairness periodically (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/instantiate over recreating a template's tasks by hand when a routine needs to run outside its normal schedule.
  • Prefer POST /api/routine-templates/:id/skip over 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/streak in reminders or notifications sparingly — it's most motivating shown right after a completion, not on every page load.