{"openapi":"3.1.0","info":{"title":"Focusly API","version":"1.0.0","description":"A 12-week-year planner. Authenticate with `Authorization: Bearer <token>`. Errors use {\"error\": {\"code\", \"message\", \"details\"?}}. List endpoints are paginated with `limit` and `cursor`."},"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true}},"required":["code","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false},"Cycle":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"start_date":{"type":"string","format":"date","example":"2026-01-05"},"end_date":{"type":"string","format":"date","example":"2026-01-05","description":"Last day of the cycle (derived)"},"length_weeks":{"type":"integer"},"week_start_day":{"type":"string","enum":["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]},"timezone":{"type":"string","description":"IANA timezone name"},"status":{"type":"string","enum":["upcoming","active","ended"],"description":"Derived from today in the cycle timezone"},"current_week":{"type":["null","integer"],"description":"1-based current week; null unless active"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","name","start_date","end_date","length_weeks","week_start_day","timezone","status","current_week","created_at","updated_at"],"additionalProperties":false},"Goal":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cycle_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["null","string"]},"position":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"progress":{"type":"object","properties":{"to_date":{"type":"object","properties":{"completed":{"type":"integer","description":"Sum over tasks of min(checkin_count, times_per_week)"},"planned":{"type":"integer","description":"Sum over tasks of times_per_week"},"percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"100 * completed / planned rounded half up (integer math), null when planned = 0"},"tasks_done":{"type":"integer","description":"Tasks with checkin_count >= times_per_week"},"tasks_planned":{"type":"integer","description":"Number of tasks"}},"required":["completed","planned","percent","tasks_done","tasks_planned"],"additionalProperties":false,"description":"Tasks in weeks 1 through the current week (all weeks once the cycle has ended, none before it starts)"},"cycle":{"type":"object","properties":{"completed":{"type":"integer","description":"Sum over tasks of min(checkin_count, times_per_week)"},"planned":{"type":"integer","description":"Sum over tasks of times_per_week"},"percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"100 * completed / planned rounded half up (integer math), null when planned = 0"},"tasks_done":{"type":"integer","description":"Tasks with checkin_count >= times_per_week"},"tasks_planned":{"type":"integer","description":"Number of tasks"}},"required":["completed","planned","percent","tasks_done","tasks_planned"],"additionalProperties":false,"description":"Tasks in every week of the cycle, including future-dated check-ins"}},"required":["to_date","cycle"],"additionalProperties":false}},"required":["id","cycle_id","title","description","position","created_at","updated_at","progress"],"additionalProperties":false},"Task":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"goal_id":{"type":"string","format":"uuid"},"cycle_id":{"type":"string","format":"uuid"},"week_number":{"type":"integer"},"week_start_date":{"type":"string","format":"date","example":"2026-01-05"},"week_end_date":{"type":"string","format":"date","example":"2026-01-05"},"title":{"type":"string"},"notes":{"type":["null","string"]},"times_per_week":{"type":"integer"},"recurrence_group_id":{"type":["null","string"],"format":"uuid","description":"Shared by the weekly copies of a recurring task"},"position":{"type":"integer"},"checkins":{"type":"array","items":{"type":"string","format":"date","example":"2026-01-05"},"description":"Checked-in dates"},"checkin_count":{"type":"integer"},"complete":{"type":"boolean","description":"checkin_count >= times_per_week"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","goal_id","cycle_id","week_number","week_start_date","week_end_date","title","notes","times_per_week","recurrence_group_id","position","checkins","checkin_count","complete","created_at","updated_at"],"additionalProperties":false},"Checkin":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"task_id":{"type":"string","format":"uuid"},"date":{"type":"string","format":"date","example":"2026-01-05"}},"required":["id","task_id","date"],"additionalProperties":false},"WeekScore":{"type":"object","properties":{"week_number":{"type":"integer"},"start_date":{"type":"string","format":"date","example":"2026-01-05"},"end_date":{"type":"string","format":"date","example":"2026-01-05"},"completed":{"type":"integer","description":"Sum over tasks of min(checkin_count, times_per_week)"},"planned":{"type":"integer","description":"Sum over tasks of times_per_week"},"percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"100 * completed / planned rounded half up (integer math), null when planned = 0"},"tasks_done":{"type":"integer","description":"Tasks with checkin_count >= times_per_week"},"tasks_planned":{"type":"integer","description":"Number of tasks"},"state":{"type":"string","enum":["past","current","future"],"description":"Where the week sits relative to today in the cycle timezone. Future weeks are reported but excluded from averages."}},"required":["week_number","start_date","end_date","completed","planned","percent","tasks_done","tasks_planned","state"],"additionalProperties":false},"AuditEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"actor":{"type":"string","description":"Label of the token that made the change"},"action":{"type":"string"},"entity_type":{"type":"string"},"entity_id":{"type":"string","format":"uuid"},"timestamp":{"type":"string","format":"date-time"},"changes":{"type":"object","additionalProperties":true}},"required":["id","actor","action","entity_type","entity_id","timestamp","changes"],"additionalProperties":false},"Token":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string"},"role":{"type":"string","enum":["owner","agent"]},"kind":{"type":"string","enum":["app","connection","cli"],"description":"app: a device signed in through a setup link; connection: an AI connection; cli: made with the admin CLI"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["null","string"],"format":"date-time"},"revoked_at":{"type":["null","string"],"format":"date-time"}},"required":["id","label","role","kind","created_at","last_used_at","revoked_at"],"additionalProperties":false}}},"paths":{"/v1/cycles":{"get":{"summary":"List cycles, ordered by start date","tags":["cycles"],"parameters":[{"schema":{"type":"string","enum":["upcoming","active","ended"]},"in":"query","name":"status","required":false},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Opaque cursor from a previous next_cursor"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Cycle"}},"next_cursor":{"type":["null","string"],"description":"Pass as cursor to get the next page; null on the last page"}},"required":["data","next_cursor"],"additionalProperties":false}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a cycle","tags":["cycles"],"description":"start_date must fall on week_start_day. Date ranges may not overlap another cycle (409).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"start_date":{"type":"string","format":"date","example":"2026-01-05","description":"Must fall on week_start_day"},"length_weeks":{"type":"integer","minimum":1,"maximum":52,"default":12},"week_start_day":{"type":"string","enum":["sunday","monday","tuesday","wednesday","thursday","friday","saturday"],"default":"monday"},"timezone":{"type":"string","minLength":1,"default":"America/New_York"}},"required":["name","start_date"],"additionalProperties":false}}}},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cycle"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/cycles/current":{"get":{"summary":"The active cycle","tags":["cycles"],"description":"Returns 404 no_active_cycle when none is active; error.details.next_cycle_id names the next upcoming cycle, if any.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cycle"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/cycles/{id}":{"get":{"summary":"Get a cycle","tags":["cycles"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cycle"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a cycle","tags":["cycles"],"description":"Works on ended cycles too; increasing length_weeks extends one. Rejected with 409 if it would overlap another live cycle or leave tasks or check-ins outside the cycle.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200},"start_date":{"type":"string","format":"date","example":"2026-01-05","description":"Must fall on week_start_day"},"length_weeks":{"type":"integer","minimum":1,"maximum":52},"week_start_day":{"type":"string","enum":["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]},"timezone":{"type":"string","minLength":1}},"required":[],"additionalProperties":false,"minProperties":1}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Cycle"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a cycle and everything in it (owner only)","tags":["cycles"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"204":{"description":"Default Response"},"403":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/cycles/{id}/weeks/{n}":{"get":{"summary":"One week: tasks grouped by goal, with check-ins, completion and the week score","tags":["cycles"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true},{"schema":{"type":"integer","minimum":1},"in":"path","name":"n","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"cycle_id":{"type":"string","format":"uuid"},"week_number":{"type":"integer"},"start_date":{"type":"string","format":"date","example":"2026-01-05"},"end_date":{"type":"string","format":"date","example":"2026-01-05"},"completed":{"type":"integer","description":"Sum over tasks of min(checkin_count, times_per_week)"},"planned":{"type":"integer","description":"Sum over tasks of times_per_week"},"percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"100 * completed / planned rounded half up (integer math), null when planned = 0"},"tasks_done":{"type":"integer","description":"Tasks with checkin_count >= times_per_week"},"tasks_planned":{"type":"integer","description":"Number of tasks"},"state":{"type":"string","enum":["past","current","future"],"description":"Where the week sits relative to today in the cycle timezone. Future weeks are reported but excluded from averages."},"goals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cycle_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["null","string"]},"position":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"tasks":{"type":"array","items":{"$ref":"#/components/schemas/Task"}}},"required":["id","cycle_id","title","description","position","created_at","updated_at","tasks"],"additionalProperties":false}}},"required":["cycle_id","week_number","start_date","end_date","completed","planned","percent","tasks_done","tasks_planned","state","goals"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/cycles/{id}/scores":{"get":{"summary":"Score for every week of the cycle","tags":["cycles"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"cycle_id":{"type":"string","format":"uuid"},"current_week":{"type":["null","integer"]},"completed":{"type":"integer","description":"Whole cycle (all weeks). Sum over tasks of min(checkin_count, times_per_week)"},"planned":{"type":"integer","description":"Whole cycle (all weeks). Sum over tasks of times_per_week"},"percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"Whole cycle (all weeks). 100 * completed / planned rounded half up (integer math), null when planned = 0"},"tasks_done":{"type":"integer","description":"Whole cycle (all weeks). Tasks with checkin_count >= times_per_week"},"tasks_planned":{"type":"integer","description":"Whole cycle (all weeks). Number of tasks"},"average_percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"Pooled over weeks that are not future and have planned > 0: sum(completed) / sum(planned) as a percent, rounded half up; null when no week qualifies"},"weeks":{"type":"array","items":{"$ref":"#/components/schemas/WeekScore"}}},"required":["cycle_id","current_week","completed","planned","percent","tasks_done","tasks_planned","average_percent","weeks"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/cycles/{id}/export":{"get":{"summary":"Full JSON export of a cycle","tags":["cycles"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"format_version":{"type":"integer"},"exported_at":{"type":"string","format":"date-time"},"cycle":{"$ref":"#/components/schemas/Cycle"},"scores":{"type":"object","properties":{"cycle_id":{"type":"string","format":"uuid"},"current_week":{"type":["null","integer"]},"completed":{"type":"integer","description":"Whole cycle (all weeks). Sum over tasks of min(checkin_count, times_per_week)"},"planned":{"type":"integer","description":"Whole cycle (all weeks). Sum over tasks of times_per_week"},"percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"Whole cycle (all weeks). 100 * completed / planned rounded half up (integer math), null when planned = 0"},"tasks_done":{"type":"integer","description":"Whole cycle (all weeks). Tasks with checkin_count >= times_per_week"},"tasks_planned":{"type":"integer","description":"Whole cycle (all weeks). Number of tasks"},"average_percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"Pooled over weeks that are not future and have planned > 0: sum(completed) / sum(planned) as a percent, rounded half up; null when no week qualifies"},"weeks":{"type":"array","items":{"$ref":"#/components/schemas/WeekScore"}}},"required":["cycle_id","current_week","completed","planned","percent","tasks_done","tasks_planned","average_percent","weeks"],"additionalProperties":false},"goals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cycle_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":["null","string"]},"position":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"goal_id":{"type":"string","format":"uuid"},"week_number":{"type":"integer"},"title":{"type":"string"},"notes":{"type":["null","string"]},"times_per_week":{"type":"integer"},"recurrence_group_id":{"type":["null","string"],"format":"uuid","description":"Shared by the weekly copies of a recurring task"},"position":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"checkins":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"date":{"type":"string","format":"date","example":"2026-01-05"}},"required":["id","date"],"additionalProperties":false}}},"required":["id","goal_id","week_number","title","notes","times_per_week","recurrence_group_id","position","created_at","updated_at","checkins"],"additionalProperties":false}}},"required":["id","cycle_id","title","description","position","created_at","updated_at","tasks"],"additionalProperties":false}}},"required":["format_version","exported_at","cycle","scores","goals"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/today":{"get":{"summary":"Active cycle, current week, this week's tasks and which are checked in today","tags":["cycles"],"description":"\"Today\" is the current date in the active cycle's timezone. 404 no_active_cycle when none is active.","responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"date":{"type":"string","format":"date","example":"2026-01-05","description":"Today's date in the cycle timezone"},"weekday":{"type":"string","enum":["sunday","monday","tuesday","wednesday","thursday","friday","saturday"]},"cycle":{"$ref":"#/components/schemas/Cycle"},"week_number":{"type":"integer"},"start_date":{"type":"string","format":"date","example":"2026-01-05"},"end_date":{"type":"string","format":"date","example":"2026-01-05"},"completed":{"type":"integer","description":"Sum over tasks of min(checkin_count, times_per_week)"},"planned":{"type":"integer","description":"Sum over tasks of times_per_week"},"percent":{"type":["null","integer"],"minimum":0,"maximum":100,"description":"100 * completed / planned rounded half up (integer math), null when planned = 0"},"tasks_done":{"type":"integer","description":"Tasks with checkin_count >= times_per_week"},"tasks_planned":{"type":"integer","description":"Number of tasks"},"state":{"type":"string","enum":["past","current","future"],"description":"Where the week sits relative to today in the cycle timezone. Future weeks are reported but excluded from averages."},"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"goal_id":{"type":"string","format":"uuid"},"cycle_id":{"type":"string","format":"uuid"},"week_number":{"type":"integer"},"week_start_date":{"type":"string","format":"date","example":"2026-01-05"},"week_end_date":{"type":"string","format":"date","example":"2026-01-05"},"title":{"type":"string"},"notes":{"type":["null","string"]},"times_per_week":{"type":"integer"},"recurrence_group_id":{"type":["null","string"],"format":"uuid","description":"Shared by the weekly copies of a recurring task"},"position":{"type":"integer"},"checkins":{"type":"array","items":{"type":"string","format":"date","example":"2026-01-05"},"description":"Checked-in dates"},"checkin_count":{"type":"integer"},"complete":{"type":"boolean","description":"checkin_count >= times_per_week"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"goal_title":{"type":"string"},"goal_position":{"type":"integer"},"checked_in_today":{"type":"boolean"}},"required":["id","goal_id","cycle_id","week_number","week_start_date","week_end_date","title","notes","times_per_week","recurrence_group_id","position","checkins","checkin_count","complete","created_at","updated_at","goal_title","goal_position","checked_in_today"],"additionalProperties":false}}},"required":["date","weekday","cycle","week_number","start_date","end_date","completed","planned","percent","tasks_done","tasks_planned","state","tasks"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/cycles/{id}/goals":{"get":{"summary":"List a cycle's goals with progress","tags":["goals"],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Opaque cursor from a previous next_cursor"},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Goal"}},"next_cursor":{"type":["null","string"],"description":"Pass as cursor to get the next page; null on the last page"}},"required":["data","next_cursor"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a goal","tags":["goals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":500},"description":{"type":["string","null"],"maxLength":5000},"position":{"type":"integer","minimum":0,"description":"Defaults to the end of the list"}},"required":["title"],"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Goal"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/goals/{id}":{"get":{"summary":"Get a goal with progress (to date and whole cycle)","tags":["goals"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Goal"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a goal","tags":["goals"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":500},"description":{"type":["string","null"],"maxLength":5000},"position":{"type":"integer","minimum":0,"description":"Defaults to the end of the list"}},"required":[],"additionalProperties":false,"minProperties":1}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Goal"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a goal and its tasks (owner only)","tags":["goals"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"204":{"description":"Default Response"},"403":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/goals/{id}/tasks":{"get":{"summary":"List a goal's tasks across all weeks","tags":["tasks"],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Opaque cursor from a previous next_cursor"},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"next_cursor":{"type":["null","string"],"description":"Pass as cursor to get the next page; null on the last page"}},"required":["data","next_cursor"],"additionalProperties":false}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a task","tags":["tasks"],"description":"With \"recurring\": true, creates one task per week from week_number through the last week, sharing a recurrence_group_id. Always returns the created tasks in data.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"week_number":{"type":"integer","minimum":1},"title":{"type":"string","minLength":1,"maxLength":500},"notes":{"type":["string","null"],"maxLength":5000},"times_per_week":{"type":"integer","minimum":1,"maximum":7,"default":1},"position":{"type":"integer","minimum":0},"recurring":{"type":"boolean","default":false,"description":"Create one copy per week from week_number to the last week of the cycle"}},"required":["week_number","title"],"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}}},"required":["data"]}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tasks/{id}":{"get":{"summary":"Get a task","tags":["tasks"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Update a task","tags":["tasks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":500},"notes":{"type":["string","null"],"maxLength":5000},"times_per_week":{"type":"integer","minimum":1,"maximum":7},"position":{"type":"integer","minimum":0}},"required":[],"additionalProperties":false,"minProperties":1}}}},"parameters":[{"schema":{"type":"string","enum":["this","following"],"default":"this"},"in":"query","name":"scope","required":false,"description":"For recurring tasks: this week only, or this and following weeks"},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a task","tags":["tasks"],"description":"Agent tokens may delete tasks only in draft cycles (403 use_app otherwise).","parameters":[{"schema":{"type":"string","enum":["this","following"],"default":"this"},"in":"query","name":"scope","required":false,"description":"For recurring tasks: this week only, or this and following weeks"},{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"204":{"description":"Default Response"},"403":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tasks/{id}/move":{"post":{"summary":"Move a task to another week","tags":["tasks"],"description":"Moves only this copy. The task's check-ins shift by the same number of days as the week change (same weekday), in the same transaction. Rejected with 409 week_taken if its recurrence group already has a copy in that week. Agent tokens get 409 week_closed when the task's week has ended.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"week_number":{"type":"integer","minimum":1}},"required":["week_number"],"additionalProperties":false}}}},"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Task"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tasks/{id}/checkins/{date}":{"put":{"summary":"Check in a task on a date (idempotent)","tags":["checkins"],"description":"The date must fall inside the task's week (422 checkin_outside_week). Returns 201 when created, 200 when it already existed. 409 weekly_target_reached (details: task_id, times_per_week, checkin_count) if a new check-in would exceed the task's times_per_week; a date that already has a check-in is still 200, even over the cap.","parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"in":"path","name":"date","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkin"}}}},"201":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkin"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Remove a check-in (idempotent)","tags":["checkins"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"in":"path","name":"date","required":true}],"responses":{"204":{"description":"Default Response"},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/audit":{"get":{"summary":"Audit log of every write, oldest first","tags":["audit"],"parameters":[{"schema":{"type":"string","format":"date-time"},"in":"query","name":"since","required":false,"description":"Only entries at or after this time"},{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Opaque cursor from a previous next_cursor"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditEntry"}},"next_cursor":{"type":["null","string"],"description":"Pass as cursor to get the next page; null on the last page"}},"required":["data","next_cursor"],"additionalProperties":false}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tokens":{"get":{"summary":"List tokens (owner only)","tags":["tokens"],"parameters":[{"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"in":"query","name":"limit","required":false},{"schema":{"type":"string"},"in":"query","name":"cursor","required":false,"description":"Opaque cursor from a previous next_cursor"}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Token"}},"next_cursor":{"type":["null","string"],"description":"Pass as cursor to get the next page; null on the last page"}},"required":["data","next_cursor"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a token (owner only). The secret is returned once.","tags":["tokens"],"description":"kind defaults to connection.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":100},"role":{"type":"string","enum":["owner","agent"]},"kind":{"type":"string","enum":["app","connection","cli"],"default":"connection"}},"required":["label","role"],"additionalProperties":false}}}},"responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string"},"role":{"type":"string","enum":["owner","agent"]},"kind":{"type":"string","enum":["app","connection","cli"],"description":"app: a device signed in through a setup link; connection: an AI connection; cli: made with the admin CLI"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["null","string"],"format":"date-time"},"revoked_at":{"type":["null","string"],"format":"date-time"},"token":{"type":"string","description":"The secret. Shown only once; store it now."}},"required":["id","label","role","kind","created_at","last_used_at","revoked_at","token"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/tokens/{id}":{"delete":{"summary":"Revoke a token (owner only)","tags":["tokens"],"parameters":[{"schema":{"type":"string","format":"uuid"},"in":"path","name":"id","required":true}],"responses":{"204":{"description":"Default Response"},"403":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/connections/pairing":{"post":{"summary":"Make a one-time pairing code for connecting Claude or ChatGPT (owner only). The code is returned once.","tags":["connections"],"description":"The owner pastes the code on the authorize page that Claude or ChatGPT opens (see the OAuth section of the README). 10 characters, valid 10 minutes, usable once; making a new one ends any older open code. A code is dead after 5 wrong guesses. Agent tokens get 403.","responses":{"201":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","description":"One-time pairing code (10 characters). Shown only once; paste it on the authorize page within expires_at. Case, spaces and dashes are ignored."},"expires_at":{"type":"string","format":"date-time"}},"required":["code","expires_at"],"additionalProperties":false}}}},"403":{"description":"Default Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/setup/redeem":{"post":{"summary":"Redeem a setup link for an app token (no auth). The secret is returned once.","tags":["setup"],"description":"Exchanges the one-time code from a setup link (issued with the CLI `create-setup-link`) for an owner-role token of kind app, labelled \"app: <device_label>\". 404 setup_link_not_found: unknown code. 410 setup_link_used / setup_link_expired: already redeemed, expired, or replaced by a newer link. 409 label_taken: an active token already has that label; the code is not consumed, so retry with a distinct device_label. 429 rate_limited with a Retry-After header (seconds): too many failed redeems from this IP (default 5 per 15 minutes), or redeem is locked for everyone after too many failures overall (default 20 per hour locks it for 15 minutes).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":128,"description":"The one-time code from the setup link (case, spaces and dashes are ignored)"},"device_label":{"type":"string","minLength":1,"maxLength":60,"pattern":"^[^\\p{C}\\p{Zl}\\p{Zp}]+$","description":"1-60 printable characters naming the device; the token is labelled \"app: <device_label>\""}},"required":["code","device_label"],"additionalProperties":false}}}},"security":[],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"label":{"type":"string"},"role":{"type":"string","enum":["owner","agent"]},"kind":{"type":"string","enum":["app","connection","cli"],"description":"app: a device signed in through a setup link; connection: an AI connection; cli: made with the admin CLI"},"created_at":{"type":"string","format":"date-time"},"last_used_at":{"type":["null","string"],"format":"date-time"},"revoked_at":{"type":["null","string"],"format":"date-time"},"token":{"type":"string","description":"The secret. Shown only once; store it now."}},"required":["id","label","role","kind","created_at","last_used_at","revoked_at","token"],"additionalProperties":false}}}},"404":{"description":"setup_link_not_found: no setup link has this code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"label_taken: an active token already has the label \"app: <device_label>\"; the code was not consumed, retry with a distinct device_label","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"setup_link_used: already redeemed; setup_link_expired: expired or replaced by a newer link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"validation_error: code missing, or device_label not 1-60 printable characters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"rate_limited: too many failed redeems. details.retry_after_seconds equals the Retry-After header.","headers":{"Retry-After":{"schema":{"type":"integer","minimum":1},"description":"Seconds to wait before retrying"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"servers":[{"url":"https://twelvewise.com/api"}],"security":[{"bearer":[]}]}