{"openapi":"3.0.0","paths":{"/api/v1/auth/device/start":{"post":{"description":"Returns a short `user_code` for the human and a secret `device_code` for the device. The user confirms the code at `verify_url`; the device polls `/poll` until then. Codes live for 10 minutes and are single-use.","operationId":"V1AuthDeviceController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1DeviceStartDto"}}}},"responses":{"200":{"description":"Pairing session created"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"summary":"Begin device pairing","tags":["API v1 — Device pairing"]}},"/api/v1/auth/device/poll":{"post":{"description":"ALL flow states are HTTP 200 with a `status` field: `pending`, `slow_down`, `denied`, `expired`, or `ok` with the freshly minted `token` (returned exactly once; the pairing record is deleted). A non-200 means only that the service itself was unreachable — constrained clients keep a single parse path.","operationId":"V1AuthDeviceController_poll","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1DevicePollDto"}}}},"responses":{"200":{"description":"Flow state"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"summary":"Poll for the pairing result","tags":["API v1 — Device pairing"]}},"/api/v1/tracker/status":{"get":{"description":"Returns the currently running time entry with activities and tags, or `null` if nothing is being tracked.","operationId":"V1TrackerController_status","parameters":[],"responses":{"200":{"description":"Active entry or null"},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `tracker:read`"}},"security":[{"bearer":[]}],"summary":"Get current active time entry","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:read"]}},"/api/v1/tracker/state":{"get":{"description":"Same information as `/status`, reduced to a few hundred bytes for constrained clients (watches, embedded devices). Includes a server-computed `elapsed_seconds` and `server_now`, so the client needs neither date parsing nor a trustworthy local clock. Timestamps are unix epoch seconds. The label is truncated to 64 characters and at most 3 tags are returned.","operationId":"V1TrackerController_state","parameters":[],"responses":{"200":{"description":"Compact state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerStateDto"}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `tracker:read`"}},"security":[{"bearer":[]}],"summary":"Get a compact tracker state","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:read"]}},"/api/v1/tracker/toggle":{"post":{"description":"Safe to retry. With `expect: \"running\"` a already-running timer is returned unchanged instead of being restarted, so a retry after a dropped connection never resets the clock. With `expect: \"stopped\"` an already-stopped timer returns 200 instead of 404. Omit `expect` to flip the current state. Returns the compact state, plus what was stopped.","operationId":"V1TrackerController_toggle","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerToggleDto"}}}},"responses":{"200":{"description":"Resulting state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerToggleResultDto"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Start or stop the timer idempotently","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:write"]}},"/api/v1/tracker/label":{"patch":{"description":"Changes the label and/or tags of the entry that is currently running, without splitting it. Previously the only way to label a running timer was to stop it with `restart`, which cut the entry in two. Pass an empty `label` to clear it.","operationId":"V1TrackerController_label","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerLabelDto"}}}},"responses":{"200":{"description":"Updated state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerStateDto"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"No active time entry"}},"security":[{"bearer":[]}],"summary":"Relabel the running entry","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:write"]},"post":{"description":"Identical to `PATCH /v1/tracker/label`. Exists for clients whose HTTP stack has no PATCH — Garmin Connect IQ supports only GET, POST, PUT and DELETE.","operationId":"V1TrackerController_labelPost","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerLabelDto"}}}},"responses":{"200":{"description":"Updated state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerStateDto"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"No active time entry"}},"security":[{"bearer":[]}],"summary":"Relabel the running entry (POST alias)","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:write"]}},"/api/v1/tracker/start":{"post":{"description":"Starts a new time entry. Optionally provide description and activities in one call. If activities are provided, the auto-created empty activity is replaced. Tags can be referenced by name or ID.","operationId":"V1TrackerController_start","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerStartDto"}}}},"responses":{"200":{"description":"Created entry with activities"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `tracker:write`"},"429":{"description":"Rate limit exceeded"}},"security":[{"bearer":[]}],"summary":"Start tracking time","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:write"]}},"/api/v1/tracker/stop":{"post":{"description":"Stops the active entry. Set `restart: true` to immediately start a new one (useful for switching tasks). When restarting, `activities` in the body are applied to the **new** entry, and if `ended_at` is given the new entry starts exactly there — the timeline stays gapless, which is how a client splits a running entry retroactively (\"the last N hours were actually sleep\").","operationId":"V1TrackerController_stop","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerStopDto"}}}},"responses":{"200":{"description":"Stopped entry and optional new entry"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing PAT token"}},"security":[{"bearer":[]}],"summary":"Stop current time entry","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:write"]}},"/api/v1/tracker/log":{"post":{"description":"Creates a finished time entry with explicit start/end times. Useful for logging work done without the tracker running.","operationId":"V1TrackerController_log","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TrackerLogDto"}}}},"responses":{"200":{"description":"Created retroactive entry"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Log a completed time entry retroactively","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:write"]}},"/api/v1/tracker/history":{"get":{"description":"Returns time entries sorted by date descending. Filter by date range and/or tag (by name or ID). Dates (YYYY-MM-DD) are whole days in the user profile time zone, date_to inclusive: date_from=2026-05-14&date_to=2026-05-14 returns the entries started on that day.","operationId":"V1TrackerController_history","parameters":[{"name":"date_from","required":false,"in":"query","description":"Entries started on or after this point. A date (YYYY-MM-DD) means the start of that day in the user profile time zone; a full ISO datetime is used as is.","schema":{"example":"2026-05-01","type":"string"}},{"name":"date_to","required":false,"in":"query","description":"Entries started on or before this point. A date (YYYY-MM-DD) includes that whole day in the user profile time zone; a full ISO datetime is used as is (inclusive). 9999-12-31 means no upper bound.","schema":{"example":"2026-05-31","type":"string"}},{"name":"tag","required":false,"in":"query","description":"Filter by tag name or ID","schema":{"example":"Development","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max entries to return","schema":{"default":50,"example":50,"type":"number"}}],"responses":{"200":{"description":"Paginated list of entries with total count"},"400":{"description":"date_from/date_to is not a valid ISO 8601 date or datetime (e.g. 2026-02-30)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"List time entries","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:read"]}},"/api/v1/tracker/tags":{"get":{"description":"Returns all time tracker tags. Defaults are unchanged: every tag, alphabetical, full objects. Constrained clients can ask for `limit`, `order=recent` (recently used first), `order=frequent` (most used over the last 90 days first) and `compact=true` (only id, name and color).","operationId":"V1TrackerController_tags","parameters":[{"name":"limit","required":false,"in":"query","description":"Maximum number of tags to return","schema":{"example":8,"type":"number"}},{"name":"order","required":false,"in":"query","description":"Sort order. `name` (default) is alphabetical; `recent` puts recently used tags first; `frequent` — most used over the last 90 days first.","schema":{"type":"string","enum":["name","recent","frequent"]}},{"name":"compact","required":false,"in":"query","description":"Return only id, name and color — roughly a third of the full payload","schema":{"example":true,"type":"boolean"}}],"responses":{"200":{"description":"List of tags"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"List tracker tags","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:read"]},"post":{"description":"Creates a tag, or returns the existing one if a tag with the same name already exists. The schema has no unique constraint on (user_id, name), so this route deduplicates by name to avoid producing duplicates on retries.","operationId":"V1TrackerController_createTag","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateTagDto"}}}},"responses":{"200":{"description":"Created or existing tag"},"400":{"description":"Empty or too long name (1-100 characters), or a color that is not #RRGGBB","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Create a tracker tag","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:write"]}},"/api/v1/tracker/today":{"get":{"description":"Tasks that are in progress, whose planned range covers the day, or that are due on the day — ranked by priority, then due date. Under a kilobyte for constrained clients (watches): titles are truncated to 48 characters and each task carries at most one tracker tag, matched by project and then by stream. Pass `date` (YYYY-MM-DD) as the client's local calendar day. Defaults to today in the profile time zone.","operationId":"V1TrackerController_today","parameters":[{"name":"limit","required":false,"in":"query","description":"Maximum number of tasks to return (default 8)","schema":{"example":8,"type":"number"}},{"name":"date","required":false,"in":"query","description":"Client's local calendar day, YYYY-MM-DD; a day that does not exist (2026-02-30) is rejected with 400. Defaults to today in the user profile time zone.","schema":{"example":"2026-08-01","type":"string"}}],"responses":{"200":{"description":"Ranked tasks","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/V1TodayTaskDto"}}}}},"400":{"description":"date is not an existing YYYY-MM-DD day","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Tasks for today, compact","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:read"]}},"/api/v1/tracker/labels":{"get":{"description":"Distinct activity titles ordered by most recent use. Intended for clients that offer a quick \"what I did last time\" pick — far cheaper than fetching history, which embeds every activity and tag.","operationId":"V1TrackerController_labels","parameters":[{"name":"limit","required":false,"in":"query","description":"Maximum number of labels to return","schema":{"example":8,"type":"number"}}],"responses":{"200":{"description":"Recent labels, most recent first"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"List recently used activity labels","tags":["API v1 — Tracker"],"x-required-scopes":["tracker:read"]}},"/api/v1/tasks":{"get":{"description":"Returns tasks with optional filters. Streams and projects can be referenced by name or numeric ID.","operationId":"V1TasksController_list","parameters":[{"name":"stream","required":false,"in":"query","description":"Filter by stream name, key, or ID","schema":{"example":"Development","type":"string"}},{"name":"project","required":false,"in":"query","description":"Filter by project name or ID","schema":{"example":"Website","type":"string"}},{"name":"status","required":false,"in":"query","description":"`done` is accepted as a legacy alias of `completed`","schema":{"example":"open","type":"string","enum":["open","in_progress","completed","cancelled"]}},{"name":"priority","required":false,"in":"query","schema":{"example":"high","type":"string","enum":["low","medium","high","urgent"]}},{"name":"search","required":false,"in":"query","description":"Case-insensitive substring of the title or the description; surrounding spaces are ignored","schema":{"maxLength":200,"example":"login","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max results to return","schema":{"minimum":1,"maximum":500,"default":50,"example":50,"type":"number"}}],"responses":{"200":{"description":"Array of tasks"},"400":{"description":"Unknown status or priority, or an invalid search or limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `tasks:read`"}},"security":[{"bearer":[]}],"summary":"List tasks","tags":["API v1 — Tasks"],"x-required-scopes":["tasks:read"]},"post":{"description":"Creates a new task. `stream` is required and can be a name, key (e.g. \"DEV\"), or numeric ID. `project` is optional and also accepts name or ID.","operationId":"V1TasksController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateTaskDto"}}}},"responses":{"201":{"description":"Created task"},"400":{"description":"Stream or project not found (unknown name/key or not your ID), a date is neither YYYY-MM-DD nor an ISO 8601 date-time, or status, priority, complexity or hours are invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Create a task","tags":["API v1 — Tasks"],"x-required-scopes":["tasks:write"]}},"/api/v1/tasks/{id}":{"get":{"operationId":"V1TasksController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":42,"type":"number"}}],"responses":{"200":{"description":"Task object"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Task not found"}},"security":[{"bearer":[]}],"summary":"Get task by ID","tags":["API v1 — Tasks"],"x-required-scopes":["tasks:read"]},"patch":{"operationId":"V1TasksController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":42,"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateTaskDto"}}}},"responses":{"200":{"description":"Updated task"},"400":{"description":"A date is neither YYYY-MM-DD nor an ISO 8601 date-time, or title, status, priority, complexity or hours are invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Task not found"}},"security":[{"bearer":[]}],"summary":"Update a task","tags":["API v1 — Tasks"],"x-required-scopes":["tasks:write"]},"delete":{"operationId":"V1TasksController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":42,"type":"number"}}],"responses":{"200":{"description":"Task deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Task not found"}},"security":[{"bearer":[]}],"summary":"Delete a task","tags":["API v1 — Tasks"],"x-required-scopes":["tasks:write"]}},"/api/v1/tasks/{id}/complete":{"post":{"description":"Shortcut to set task status to `completed`.","operationId":"V1TasksController_complete","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":42,"type":"number"}}],"responses":{"200":{"description":"Completed task"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Task not found"}},"security":[{"bearer":[]}],"summary":"Complete a task","tags":["API v1 — Tasks"],"x-required-scopes":["tasks:write"]}},"/api/v1/projects":{"get":{"description":"Returns projects with optional stream and status filters. Stream can be referenced by name, key, or ID.","operationId":"V1ProjectsController_list","parameters":[{"name":"stream","required":false,"in":"query","description":"Filter by stream name, key, or ID","schema":{"example":"Development","type":"string"}},{"name":"status","required":false,"in":"query","description":"`paused` is accepted as an alias of `on_hold`","schema":{"example":"active","type":"string","enum":["planning","active","on_hold","completed","cancelled"]}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":500,"default":50,"example":50,"type":"number"}}],"responses":{"200":{"description":"Array of projects with tasks"},"400":{"description":"Unknown status or an invalid limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `projects:read`"}},"security":[{"bearer":[]}],"summary":"List projects","tags":["API v1 — Projects"],"x-required-scopes":["projects:read"]},"post":{"description":"`stream` is required and can be a name, key, or numeric ID.","operationId":"V1ProjectsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateProjectDto"}}}},"responses":{"201":{"description":"Created project"},"400":{"description":"Stream not found (unknown name/key or not your ID), or an invalid name, priority or color","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Create a project","tags":["API v1 — Projects"],"x-required-scopes":["projects:write"]}},"/api/v1/projects/{id}":{"get":{"operationId":"V1ProjectsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":5,"type":"number"}}],"responses":{"200":{"description":"Project object with tasks"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Project not found"}},"security":[{"bearer":[]}],"summary":"Get project by ID","tags":["API v1 — Projects"],"x-required-scopes":["projects:read"]},"patch":{"operationId":"V1ProjectsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":5,"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateProjectDto"}}}},"responses":{"200":{"description":"Updated project"},"400":{"description":"Invalid name, status, priority or color","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Project not found"}},"security":[{"bearer":[]}],"summary":"Update a project","tags":["API v1 — Projects"],"x-required-scopes":["projects:write"]}},"/api/v1/streams":{"get":{"description":"Returns all active streams. Streams are top-level categories (e.g. \"Development\", \"Design\").","operationId":"V1StreamsController_list","parameters":[],"responses":{"200":{"description":"Array of streams"},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `streams:read`"}},"security":[{"bearer":[]}],"summary":"List active streams","tags":["API v1 — Streams"],"x-required-scopes":["streams:read"]},"post":{"description":"`key` is a short code used in task IDs (e.g. key \"DEV\" produces tasks like DEV-1, DEV-2).","operationId":"V1StreamsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateStreamDto"}}}},"responses":{"201":{"description":"Created stream"},"400":{"description":"Invalid name, key (1-5 characters A-Z or 0-9) or color (#RRGGBB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"409":{"description":"Stream key is already used by another stream of yours"}},"security":[{"bearer":[]}],"summary":"Create a stream","tags":["API v1 — Streams"],"x-required-scopes":["streams:write"]}},"/api/v1/streams/{id}":{"get":{"operationId":"V1StreamsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":1,"type":"number"}}],"responses":{"200":{"description":"Stream object"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Stream not found"}},"security":[{"bearer":[]}],"summary":"Get stream by ID","tags":["API v1 — Streams"],"x-required-scopes":["streams:read"]},"patch":{"description":"Set `is_active: false` to archive a stream.","operationId":"V1StreamsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":1,"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateStreamDto"}}}},"responses":{"200":{"description":"Updated stream"},"400":{"description":"Invalid name or color (#RRGGBB)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Stream not found"}},"security":[{"bearer":[]}],"summary":"Update a stream","tags":["API v1 — Streams"],"x-required-scopes":["streams:write"]}},"/api/v1/notes":{"get":{"description":"Returns notes with optional filters by stream, project (by name or ID), and text search.","operationId":"V1NotesController_list","parameters":[{"name":"stream","required":false,"in":"query","description":"Filter by stream name, key, or ID","schema":{"example":"Development","type":"string"}},{"name":"project","required":false,"in":"query","description":"Filter by project name or ID","schema":{"example":"Website","type":"string"}},{"name":"search","required":false,"in":"query","description":"Search in title and content","schema":{"example":"roadmap","type":"string"}},{"name":"limit","required":false,"in":"query","description":"Max results to return","schema":{"minimum":1,"maximum":500,"default":50,"example":50,"type":"number"}},{"name":"include_archived","required":false,"in":"query","description":"Include archived notes","schema":{"default":false,"example":false,"type":"boolean"}}],"responses":{"200":{"description":"Array of notes"},"400":{"description":"Invalid limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `notes:read`"}},"security":[{"bearer":[]}],"summary":"List notes","tags":["API v1 — Notes"],"x-required-scopes":["notes:read"]},"post":{"description":"Stream and project can be referenced by name or numeric ID. Link to a task via `issue_id`.","operationId":"V1NotesController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateNoteDto"}}}},"responses":{"201":{"description":"Created note"},"400":{"description":"Stream or project not found (unknown name/key or not your ID)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Task `issue_id` not found"}},"security":[{"bearer":[]}],"summary":"Create a note","tags":["API v1 — Notes"],"x-required-scopes":["notes:write"]}},"/api/v1/notes/{id}":{"get":{"operationId":"V1NotesController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":10,"type":"number"}}],"responses":{"200":{"description":"Note object"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Note not found"}},"security":[{"bearer":[]}],"summary":"Get note by ID","tags":["API v1 — Notes"],"x-required-scopes":["notes:read"]},"patch":{"operationId":"V1NotesController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":10,"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateNoteDto"}}}},"responses":{"200":{"description":"Updated note"},"400":{"description":"Either title or content is required: the update would leave the note without title, content and attachments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Note not found"}},"security":[{"bearer":[]}],"summary":"Update a note","tags":["API v1 — Notes"],"x-required-scopes":["notes:write"]},"delete":{"operationId":"V1NotesController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":10,"type":"number"}}],"responses":{"200":{"description":"Note deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Note not found"}},"security":[{"bearer":[]}],"summary":"Delete a note","tags":["API v1 — Notes"],"x-required-scopes":["notes:write"]}},"/api/v1/notes/{id}/archive":{"post":{"operationId":"V1NotesController_archive","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":10,"type":"number"}}],"responses":{"200":{"description":"Archived note"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Note not found"}},"security":[{"bearer":[]}],"summary":"Archive a note","tags":["API v1 — Notes"],"x-required-scopes":["notes:write"]}},"/api/v1/notes/{id}/unarchive":{"post":{"operationId":"V1NotesController_unarchive","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":10,"type":"number"}}],"responses":{"200":{"description":"Unarchived note"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Note not found"}},"security":[{"bearer":[]}],"summary":"Unarchive a note","tags":["API v1 — Notes"],"x-required-scopes":["notes:write"]}},"/api/v1/profile":{"get":{"description":"Returns basic profile info and the list of all available API scopes. Useful for checking who the token belongs to. `timezone` is the IANA time zone the server uses for \"today\" and day boundaries; `today` is the current date in it.","operationId":"V1ProfileController_get","parameters":[],"responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"number","example":1},"username":{"type":"string","example":"artem"},"name":{"type":"string","example":"Artem","nullable":true},"last_name":{"type":"string","example":"Romanchuk","nullable":true},"email":{"type":"string","example":"user@example.com"},"timezone":{"type":"string","example":"Europe/Moscow","description":"IANA time zone; \"today\" and day boundaries are computed in it"},"today":{"type":"string","example":"2026-10-05","description":"Today in the profile time zone, YYYY-MM-DD"},"available_scopes":{"type":"array","items":{"type":"string"},"example":["tracker:read","tracker:write"]}}}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `profile:read`"}},"security":[{"bearer":[]}],"summary":"Get current user profile","tags":["API v1 — Profile"],"x-required-scopes":["profile:read"]}},"/api/v1/events":{"get":{"description":"Returns events that overlap the range [start, end): `end` is exclusive, so for one day pass start=D, end=D+1. If no range is given, returns all events. Hidden calendars are automatically filtered out. Timed events are matched as moments (a bare date is midnight in the user profile timezone). All-day events are matched by calendar dates taken from the wall clock of `start`/`end`, so with `Z` boundaries an all-day event on the edge of the range may be included or left out by one day.","operationId":"V1EventsController_list","parameters":[{"name":"start","required":false,"in":"query","description":"Range start: ISO 8601 with `Z` or an offset, a date-time without an offset (user profile timezone) or a date `YYYY-MM-DD` (midnight in the user profile timezone). The range applies only when both `start` and `end` are given; `start` alone returns all events","schema":{"example":"2026-05-01","type":"string"}},{"name":"end","required":false,"in":"query","description":"Range end, exclusive; same formats as `start`. For the single day 2026-05-01 pass start=2026-05-01, end=2026-05-02","schema":{"example":"2026-05-02","type":"string"}}],"responses":{"200":{"description":"Array of calendar events"},"400":{"description":"`start` or `end` is neither a date YYYY-MM-DD nor an ISO 8601 date-time","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `events:read`"}},"security":[{"bearer":[]}],"summary":"List calendar events","tags":["API v1 — Events"],"x-required-scopes":["events:read"]},"post":{"description":"Creates a single (non-recurring) event. If `calendar_id` is not provided, the default calendar is used (the first visible one; created automatically if the user has no calendars). All-day event: `all_day: true` with dates `YYYY-MM-DD`, `end_date` is the last day (inclusive). Timed event: ISO 8601 with an offset; without an offset the time is taken in the user profile timezone.","operationId":"V1EventsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateEventDto"}}}},"responses":{"201":{"description":"Created event"},"400":{"description":"Invalid title, dates, color (#RRGGBB) or type; `end_date` before `start_date` (an equal end is allowed): \"end_date must not be before start_date\"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Calendar not found"}},"security":[{"bearer":[]}],"summary":"Create a calendar event","tags":["API v1 — Events"],"x-required-scopes":["events:write"]}},"/api/v1/events/{id}":{"get":{"operationId":"V1EventsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":15,"type":"number"}}],"responses":{"200":{"description":"Event object"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Event not found"}},"security":[{"bearer":[]}],"summary":"Get event by ID","tags":["API v1 — Events"],"x-required-scopes":["events:read"]},"patch":{"description":"`calendar_id: null` does not detach the event from its calendar — see the field description. Dates follow the same rules as in create; a GET response sent back unchanged keeps the dates. For the first event of a recurring series only this event is changed (its dates move just this event); `title`, `description`, `location`, `color`, `type` and `calendar_id` are also copied to the following occurrences. The schedule of a series is not changed through this API. Imported events (non-empty `external_source`: an iCal or Bitrix24 calendar import) are managed by their source: only `type` can be changed; `title`, `description`, `location`, dates, `all_day`, `color` or `calendar_id` with a value different from the current one give 400 (values returned by GET may be sent back unchanged).","operationId":"V1EventsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":15,"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateEventDto"}}}},"responses":{"200":{"description":"Updated event"},"400":{"description":"Invalid title, dates, color (#RRGGBB) or type; `end_date` before `start_date`: \"end_date must not be before start_date\" (a timed event moved by `start_date` alone past its current end too: send both dates); or a source field of an imported event was changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Event or calendar not found"}},"security":[{"bearer":[]}],"summary":"Update an event","tags":["API v1 — Events"],"x-required-scopes":["events:write"]},"delete":{"description":"Deletes a single event. For recurring series, only this occurrence is removed. Imported events (non-empty `external_source`) cannot be deleted — they would come back with the next sync (400); events left over from a removed calendar import are deleted as usual.","operationId":"V1EventsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":15,"type":"number"}}],"responses":{"200":{"description":"Event deleted"},"400":{"description":"The event is imported from an external calendar","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Event not found"}},"security":[{"bearer":[]}],"summary":"Delete an event","tags":["API v1 — Events"],"x-required-scopes":["events:write"]}},"/api/v1/habits":{"get":{"description":"Returns all habits (active, planned, completed) sorted by status and creation date.","operationId":"V1HabitsController_list","parameters":[],"responses":{"200":{"description":"Array of habits"},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `habits:read`"}},"security":[{"bearer":[]}],"summary":"List all habits","tags":["API v1 — Habits"],"x-required-scopes":["habits:read"]},"post":{"description":"Creates a new habit. Set `planned: true` to create in planned status (not yet active). `frequency_days` is an array of weekdays (0=Mon..6=Sun); null or an empty array means every day.","operationId":"V1HabitsController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateHabitDto"}}}},"responses":{"201":{"description":"Created habit"},"400":{"description":"Invalid field: empty or too long name, frequency_count outside 1..20, target_days outside 1..365, a weekday outside 0..6 or repeated, color not #RRGGBB, reminder_time not HH:MM","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Create a habit","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]}},"/api/v1/habits/summary":{"get":{"description":"Returns daily stats: how many habits were completed vs total scheduled, for each day in the range. Days are calendar days (YYYY-MM-DD) in the user profile time zone. The range is at most 3660 days, inclusive.","operationId":"V1HabitsController_summary","parameters":[{"name":"from","required":true,"in":"query","description":"From date, YYYY-MM-DD only (a datetime is rejected with 400)","schema":{"example":"2026-09-01","type":"string"}},{"name":"to","required":true,"in":"query","description":"To date, YYYY-MM-DD only. The range is at most 3660 days, inclusive; a longer one is rejected with 400.","schema":{"example":"2026-09-30","type":"string"}}],"responses":{"200":{"description":"Array of { date, completed, total }"},"400":{"description":"from/to is not an existing YYYY-MM-DD date, or the range is longer than 3660 days","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Daily completion summary","tags":["API v1 — Habits"],"x-required-scopes":["habits:read"]}},"/api/v1/habits/{id}":{"get":{"operationId":"V1HabitsController_get","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"responses":{"200":{"description":"Habit object with streak info"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"}},"security":[{"bearer":[]}],"summary":"Get habit by ID","tags":["API v1 — Habits"],"x-required-scopes":["habits:read"]},"patch":{"operationId":"V1HabitsController_update","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateHabitDto"}}}},"responses":{"200":{"description":"Updated habit"},"400":{"description":"Invalid field (same rules as in create); null in name, frequency_count, target_days or color","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"}},"security":[{"bearer":[]}],"summary":"Update a habit","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]},"delete":{"operationId":"V1HabitsController_remove","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"responses":{"200":{"description":"Habit deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"}},"security":[{"bearer":[]}],"summary":"Delete a habit and all its logs","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]}},"/api/v1/habits/{id}/logs":{"get":{"description":"Returns daily check-in logs for a specific habit. Defaults to the full range since habit start up to today in the user profile time zone.","operationId":"V1HabitsController_logs","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}},{"name":"from","required":false,"in":"query","description":"From date, YYYY-MM-DD only (a datetime is rejected with 400). Defaults to the habit start date.","schema":{"example":"2026-09-01","type":"string"}},{"name":"to","required":false,"in":"query","description":"To date, YYYY-MM-DD only (a datetime is rejected with 400). Defaults to today in the user profile time zone.","schema":{"example":"2026-09-30","type":"string"}}],"responses":{"200":{"description":"Array of log entries (date + count)"},"400":{"description":"from/to is not an existing YYYY-MM-DD date","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"}},"security":[{"bearer":[]}],"summary":"Get check-in logs","tags":["API v1 — Habits"],"x-required-scopes":["habits:read"]}},"/api/v1/habits/{id}/checkin":{"post":{"description":"Records one completion for today in the user profile time zone. Call multiple times for habits with `frequency_count > 1`.","operationId":"V1HabitsController_checkin","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"responses":{"200":{"description":"`{habit, log}`: `log` is today's entry, `habit.today_count` and `habit.today_done` are today's progress (`today_done` — `frequency_count` is reached). `current_streak` and `total_done` change when the day closes (shortly after midnight in the profile time zone), as in GET /v1/habits/:id"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"},"409":{"description":"Not recorded: `Habit is not active` (planned, completed or archived), `Today is not a scheduled day for this habit` (frequency_days), `Today's target is already reached` (frequency_count)"}},"security":[{"bearer":[]}],"summary":"Check in","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]}},"/api/v1/habits/{id}/undo-checkin":{"post":{"description":"Removes one completion from today (user profile time zone). If count reaches 0, the log entry is deleted.","operationId":"V1HabitsController_undoCheckin","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"responses":{"200":{"description":"`{habit, log}`: `log` is today's entry or null if removed, `habit.today_count` and `habit.today_done` — today's progress after the undo"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"}},"security":[{"bearer":[]}],"summary":"Undo check-in","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]}},"/api/v1/habits/{id}/activate":{"post":{"description":"Moves a habit from `planned` to `active` status and resets the start date to today in the user profile time zone.","operationId":"V1HabitsController_activate","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"responses":{"200":{"description":"Activated habit"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"},"409":{"description":"Habit is not in planned status"}},"security":[{"bearer":[]}],"summary":"Activate a planned habit","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]}},"/api/v1/habits/{id}/postpone":{"post":{"description":"Moves an active habit back to `planned` status. Resets current streak but keeps total stats.","operationId":"V1HabitsController_postpone","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"responses":{"200":{"description":"Postponed habit"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"},"409":{"description":"Habit is not active"}},"security":[{"bearer":[]}],"summary":"Postpone a habit","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]}},"/api/v1/habits/{id}/restart":{"post":{"description":"Fully resets the habit: clears all logs, resets streak and total to 0, sets status to active with today (user profile time zone) as start.","operationId":"V1HabitsController_restart","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":3,"type":"number"}}],"responses":{"200":{"description":"Restarted habit"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Habit not found"}},"security":[{"bearer":[]}],"summary":"Restart a habit","tags":["API v1 — Habits"],"x-required-scopes":["habits:write"]}},"/api/v1/nutrition/food-items":{"get":{"description":"Returns food items (global approved + user custom). Filter by name search and/or category.","operationId":"V1NutritionController_foodItems","parameters":[{"name":"search","required":false,"in":"query","description":"Search by name","schema":{"type":"string"}},{"name":"category","required":false,"in":"query","description":"Filter by category","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of food items with nutritional data per serving"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `nutrition:read`"}},"security":[{"bearer":[]}],"summary":"Search food items","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]},"post":{"description":"Creates a personal food item. Nutritional values are per serving (default serving = 100g).","operationId":"V1NutritionController_createFoodItem","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateFoodItemDto"}}}},"responses":{"201":{"description":"Created food item"},"400":{"description":"Empty or too long name, negative macros or a serving size that is not positive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Create a custom food item","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:write"]}},"/api/v1/nutrition/food-items/categories":{"get":{"description":"Returns distinct category names of food items visible to the user (global approved + own).","operationId":"V1NutritionController_foodCategories","parameters":[],"responses":{"200":{"description":"Array of category strings"}},"security":[{"bearer":[]}],"summary":"List food categories","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]}},"/api/v1/nutrition/food-items/{id}":{"delete":{"operationId":"V1NutritionController_removeFoodItem","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":100,"type":"number"}}],"responses":{"200":{"description":"Food item deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"403":{"description":"Cannot delete global food items"},"404":{"description":"Food item not found (or belongs to another user)"},"409":{"description":"Food item is used in recipes — remove it from them first"}},"security":[{"bearer":[]}],"summary":"Delete a custom food item","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:write"]}},"/api/v1/nutrition/meal-types":{"get":{"description":"Returns meal types (e.g. Breakfast, Lunch, Dinner, Snack). Auto-creates defaults on first access.","operationId":"V1NutritionController_mealTypes","parameters":[],"responses":{"200":{"description":"Array of meal types with id, name, sort_order"}},"security":[{"bearer":[]}],"summary":"List meal types","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]}},"/api/v1/nutrition/day-log":{"get":{"description":"Returns food logs grouped by meal type for the specified date (default: today in the user profile time zone). Each log includes calculated calories, protein, fat, carbs.","operationId":"V1NutritionController_dayLog","parameters":[{"name":"date","required":false,"in":"query","description":"Calendar day YYYY-MM-DD. Defaults to today in the user profile time zone (see GET /v1/profile). An empty value means today too.","schema":{"example":"2026-05-14","type":"string"}}],"responses":{"200":{"description":"Array of { mealType, logs[] }. Logs whose meal type was deleted come in an extra group with mealType.id = 0 (\"Без приёма пищи\"). Each log has item_name — a snapshot of the food/recipe name."},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Get food diary for a day","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]}},"/api/v1/nutrition/food-logs":{"post":{"description":"Records eating a food item or recipe. Provide either `food_item_id` or `recipe_id` (visible to the user: global approved or own food items; own or published recipes). `meal_type_id` must be one of the user's meal types. Calories/macros are calculated automatically from portions. `date` (YYYY-MM-DD) defaults to today in the user profile time zone.","operationId":"V1NutritionController_addFoodLog","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateFoodLogDto"}}}},"responses":{"201":{"description":"Created food log with calculated macros"},"400":{"description":"Portions less than 0.1, an ID that is not a positive integer, or an invalid date","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Meal type, food item or recipe not found (or not accessible)"}},"security":[{"bearer":[]}],"summary":"Log food consumption","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:write"]}},"/api/v1/nutrition/food-logs/{id}":{"delete":{"operationId":"V1NutritionController_removeFoodLog","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":50,"type":"number"}}],"responses":{"200":{"description":"Log entry deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Log entry not found"}},"security":[{"bearer":[]}],"summary":"Delete a food log entry","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:write"]}},"/api/v1/nutrition/water":{"get":{"description":"Returns water log entries and total_ml for the specified date (default: today in the user profile time zone).","operationId":"V1NutritionController_waterLogs","parameters":[{"name":"date","required":false,"in":"query","description":"Calendar day YYYY-MM-DD. Defaults to today in the user profile time zone (see GET /v1/profile). An empty value means today too.","schema":{"example":"2026-05-14","type":"string"}}],"responses":{"200":{"description":"{ logs: WaterLog[], total_ml: number }"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Get water intake for a day","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]},"post":{"description":"`date` (YYYY-MM-DD) defaults to today in the user profile time zone.","operationId":"V1NutritionController_addWaterLog","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateWaterLogDto"}}}},"responses":{"201":{"description":"Created water log"},"400":{"description":"amount_ml is not a whole number from 1 to 10000, or an invalid date","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Log water intake","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:write"]}},"/api/v1/nutrition/water/{id}":{"delete":{"operationId":"V1NutritionController_removeWaterLog","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":20,"type":"number"}}],"responses":{"200":{"description":"Water log deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Water log not found"}},"security":[{"bearer":[]}],"summary":"Delete a water log entry","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:write"]}},"/api/v1/nutrition/goals":{"get":{"description":"Returns daily targets for calories, protein, fat, carbs, and water. Auto-creates defaults on first access.","operationId":"V1NutritionController_goals","parameters":[],"responses":{"200":{"description":"Nutrition goals object"}},"security":[{"bearer":[]}],"summary":"Get nutrition goals","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]},"patch":{"description":"Only the fields present in the body are changed. The response has the same shape as GET /v1/nutrition/goals. A goal can be named as in GET (`calories`, `protein_g`, `fat_g`, `carbs_g`, `water_ml`) or with the `_target` name; both names of one goal in the same request give 400.","operationId":"V1NutritionController_updateGoals","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateGoalsDto"}}}},"responses":{"200":{"description":"Updated goals: { user_id, calories, protein_g, fat_g, carbs_g, water_ml }"},"400":{"description":"A value is null or not an integer from 0 to 100000, or one goal is passed under both names","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Update nutrition goals","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:write"]}},"/api/v1/nutrition/recipes":{"get":{"description":"Returns user recipes and approved public recipes. Each includes ingredients and calculated macros per serving.","operationId":"V1NutritionController_recipes","parameters":[{"name":"search","required":false,"in":"query","description":"Search by name","schema":{"type":"string"}}],"responses":{"200":{"description":"Array of recipes with ingredients"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"List recipes","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]}},"/api/v1/nutrition/recipes/{id}":{"get":{"operationId":"V1NutritionController_recipe","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":5,"type":"number"}}],"responses":{"200":{"description":"Recipe with ingredients and macros"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Recipe not found (or private recipe of another user)"}},"security":[{"bearer":[]}],"summary":"Get recipe by ID","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]}},"/api/v1/nutrition/fridge":{"get":{"description":"Returns items currently in the fridge with portion counts. Portions are auto-decremented when food is logged.","operationId":"V1NutritionController_fridge","parameters":[],"responses":{"200":{"description":"Array of fridge items"}},"security":[{"bearer":[]}],"summary":"List fridge items","tags":["API v1 — Nutrition"],"x-required-scopes":["nutrition:read"]}},"/api/v1/finance/accounts":{"get":{"description":"Returns all accounts with current calculated balances. Balance is in currency units (e.g. rubles, not kopecks).","operationId":"V1FinanceController_listAccounts","parameters":[],"responses":{"200":{"description":"Array of accounts with current_balance field"},"401":{"description":"Invalid or missing PAT token"},"403":{"description":"Token missing required scope `finance:read`"}},"security":[{"bearer":[]}],"summary":"List finance accounts","tags":["API v1 — Finance"],"x-required-scopes":["finance:read"]}},"/api/v1/finance/accounts/{id}":{"get":{"operationId":"V1FinanceController_getAccount","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":1,"type":"number"}}],"responses":{"200":{"description":"Account with current_balance"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Account not found"}},"security":[{"bearer":[]}],"summary":"Get account by ID with current balance","tags":["API v1 — Finance"],"x-required-scopes":["finance:read"]}},"/api/v1/finance/categories":{"get":{"description":"Returns all categories used to classify transactions (e.g. Food, Transport, Salary).","operationId":"V1FinanceController_listCategories","parameters":[],"responses":{"200":{"description":"Array of categories"}},"security":[{"bearer":[]}],"summary":"List finance categories","tags":["API v1 — Finance"],"x-required-scopes":["finance:read"]}},"/api/v1/finance/transactions":{"get":{"description":"Returns transactions with optional filters and pagination. Amount is in currency units (rubles). Sorted by date descending.","operationId":"V1FinanceController_listTransactions","parameters":[{"name":"account_id","required":false,"in":"query","description":"Account ID: transactions of this account plus transfers into it. An incoming transfer keeps the source account in account_id and has this account in to_account_id; its amount is credited to this account","schema":{"minimum":1,"type":"number"}},{"name":"category_id","required":false,"in":"query","schema":{"minimum":1,"type":"number"}},{"name":"type","required":false,"in":"query","description":"Transfers and corrections made in the cabinet are listed too","schema":{"type":"string","enum":["income","expense","transfer","correction"]}},{"name":"date_from","required":false,"in":"query","description":"From date YYYY-MM-DD, inclusive","schema":{"example":"2026-05-01","type":"string"}},{"name":"date_to","required":false,"in":"query","description":"To date YYYY-MM-DD, inclusive","schema":{"example":"2026-05-31","type":"string"}},{"name":"page","required":false,"in":"query","schema":{"minimum":1,"default":1,"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":500,"default":50,"type":"number"}}],"responses":{"200":{"description":"{ items: Transaction[], total: number, page: number, limit: number }"},"400":{"description":"Invalid filter (type, date_from/date_to YYYY-MM-DD) or page/limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"List transactions","tags":["API v1 — Finance"],"x-required-scopes":["finance:read"]},"post":{"description":"Creates an income or expense transaction. `amount` is in currency units (e.g. 1500.50 for 1500 rubles 50 kopecks). Multi-currency is supported — amount_rub is calculated automatically.","operationId":"V1FinanceController_createTransaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateTransactionDto"}}}},"responses":{"201":{"description":"Created transaction with amount in currency units"},"400":{"description":"Invalid type, date, currency or description, or the currency differs from the account currency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Account not found"}},"security":[{"bearer":[]}],"summary":"Create a transaction","tags":["API v1 — Finance"],"x-required-scopes":["finance:write"]}},"/api/v1/finance/transactions/{id}":{"get":{"operationId":"V1FinanceController_getTransaction","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":100,"type":"number"}}],"responses":{"200":{"description":"Transaction object"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Transaction not found"}},"security":[{"bearer":[]}],"summary":"Get transaction by ID","tags":["API v1 — Finance"],"x-required-scopes":["finance:read"]},"patch":{"operationId":"V1FinanceController_updateTransaction","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":100,"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateTransactionDto"}}}},"responses":{"200":{"description":"Updated transaction"},"400":{"description":"Invalid type, date, currency or description, or the currency differs from the account currency","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Transaction not found"}},"security":[{"bearer":[]}],"summary":"Update a transaction","tags":["API v1 — Finance"],"x-required-scopes":["finance:write"]},"delete":{"operationId":"V1FinanceController_removeTransaction","parameters":[{"name":"id","required":true,"in":"path","schema":{"example":100,"type":"number"}}],"responses":{"200":{"description":"Transaction deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Transaction not found"}},"security":[{"bearer":[]}],"summary":"Delete a transaction","tags":["API v1 — Finance"],"x-required-scopes":["finance:write"]}},"/api/v1/planner/preparation-stats":{"get":{"description":"Returns projects without ICE scores, tasks without estimates/complexity, and tasks without projects. Use to check readiness before generating a plan.","operationId":"V1PlannerController_getPreparationStats","parameters":[{"name":"stream_ids","required":false,"in":"query","description":"Comma-separated stream IDs to filter","schema":{"type":"string"}}],"responses":{"200":{"description":"Preparation stats"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Get preparation stats for planning","tags":["API v1 — Planner"],"x-required-scopes":["tasks:read"]}},"/api/v1/planner/settings":{"get":{"description":"Returns planner settings for the current user (working hours, schedule, energy levels). Times are wall time in the user's profile time zone.","operationId":"V1PlannerController_getSettings","parameters":[],"responses":{"200":{"description":"Planner settings object"}},"security":[{"bearer":[]}],"summary":"Get planner settings","tags":["API v1 — Planner"],"x-required-scopes":["tasks:read"]},"patch":{"description":"Working hours (HH:mm) are wall time in the user's profile time zone.","operationId":"V1PlannerController_updateSettings","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSettingsDto"}}}},"responses":{"200":{"description":"Updated settings"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Update planner settings","tags":["API v1 — Planner"],"x-required-scopes":["tasks:write"]}},"/api/v1/planner/generate":{"post":{"description":"Creates a draft plan by scheduling eligible tasks into available time slots. Tasks must have estimated_hours, complexity, and belong to a project with ICE scores. date_from/date_to (YYYY-MM-DD, at most 92 days inclusive), working hours, work days and 'today' are in the user's profile time zone (see GET /v1/profile). Items carry start_date/end_date (UTC ISO) and local_date, local_start, local_end (YYYY-MM-DDTHH:mm, wall time in the plan time zone); the response has timezone. Drafts not applied within 7 days and cancelled plans older than 7 days are deleted automatically; GET and apply of such a session return 404.","operationId":"V1PlannerController_generate","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeneratePlanDto"}}}},"responses":{"201":{"description":"Generated plan: session, timezone, items, overflow, stats"},"400":{"description":"Bad dates: wrong format, start after end, longer than 92 days, or entirely in the past","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Generate a plan","tags":["API v1 — Planner"],"x-required-scopes":["tasks:write"]}},"/api/v1/planner/sessions/{id}":{"get":{"description":"Items carry local_date, local_start and local_end in the plan time zone (session.settings_snapshot.timezone, or the profile time zone for older plans); the response has timezone.","operationId":"V1PlannerController_getSession","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Session with timezone and plan items"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Session not found"}},"security":[{"bearer":[]}],"summary":"Get plan session with items","tags":["API v1 — Planner"],"x-required-scopes":["tasks:read"]},"delete":{"operationId":"V1PlannerController_deleteSession","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"204":{"description":"Deleted"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Delete a plan session","tags":["API v1 — Planner"],"x-required-scopes":["tasks:write"]}},"/api/v1/planner/sessions/{id}/apply":{"post":{"description":"Creates calendar events for all planned items.","operationId":"V1PlannerController_applySession","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Applied session"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"404":{"description":"Draft session not found"}},"security":[{"bearer":[]}],"summary":"Apply plan to calendar","tags":["API v1 — Planner"],"x-required-scopes":["tasks:write"]}},"/api/v1/bitrix24":{"get":{"description":"Returns all B24 integrations for the user. The webhook secret is never returned: `webhook_url` comes masked (`https://portal.bitrix24.ru/rest/7/****/`). `stream_archived: true` — the stream is archived: sync does not create new tasks in it (linked tasks are still updated, but sync does not change the status of tasks in an archived stream and does not send their cancellation to Bitrix24), `warning` explains it (null when the stream is active).","operationId":"V1Bitrix24Controller_list","parameters":[],"responses":{"200":{"description":"Array of integrations (webhook_url masked)"}},"security":[{"bearer":[]}],"summary":"List Bitrix24 integrations","tags":["API v1 — Bitrix24"],"x-required-scopes":["tasks:read"]}},"/api/v1/bitrix24/{id}/status":{"get":{"description":"Returns sync status for a specific B24 integration. `sync_error` never contains the webhook key; `sync_started_at` is when the current (or last) sync started. `stream_archived: true` — the stream is archived: sync does not create new tasks in it (linked tasks are still updated, but sync does not change the status of tasks in an archived stream and does not send their cancellation to Bitrix24), `warning` explains it (null otherwise).","operationId":"V1Bitrix24Controller_status","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Integration details with sync status"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Get sync status","tags":["API v1 — Bitrix24"],"x-required-scopes":["tasks:read"]}},"/api/v1/bitrix24/{id}/sync":{"post":{"description":"Manually trigger B24 sync (pull + push). Works for a paused integration too. Only one sync of an integration runs at a time; a sync is limited to 4 minutes.","operationId":"V1Bitrix24Controller_sync","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":"Sync result: `pull` — created, updated, conflicts, skipped, missing, restored, missing_suspect, missing_check_failed, archived_skipped; `push` — pushed, failed, created, updated, uncertain. A linked task that is no longer visible in Bitrix24 (deleted or no access) gets issue sync_status `remote_missing` (`missing`); it is not pushed until it is visible again (`restored`). `missing_suspect` / `missing_check_failed` — linked tasks were not checked, nothing was marked. `archived_skipped` — new Bitrix24 tasks not created because the stream is archived"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"409":{"description":"Sync is already in progress"},"502":{"description":"Sync failed: Bitrix24 rejected the request or did not respond, e.g. \"Sync failed: B24: INVALID_CREDENTIALS — … (HTTP 401)\", \"Sync failed: B24: no response within 30 s\""}},"security":[{"bearer":[]}],"summary":"Trigger sync","tags":["API v1 — Bitrix24"],"x-required-scopes":["tasks:write"]}},"/api/v1/bitrix24/issues/{issueId}/resolve-conflict":{"post":{"description":"Mark a task conflict as resolved (accept current values)","operationId":"V1Bitrix24Controller_resolveConflict","parameters":[{"name":"issueId","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Resolve sync conflict","tags":["API v1 — Bitrix24"],"x-required-scopes":["tasks:write"]}},"/api/v1/kb/tree":{"get":{"operationId":"V1KnowledgeBaseController_tree","parameters":[{"name":"stream","required":false,"in":"query","description":"Filter tree by stream name/key/ID","schema":{"type":"string"}},{"name":"parent_id","required":false,"in":"query","description":"Subtree starting from folder ID","schema":{"type":"number"}}],"responses":{"200":{"description":""},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Get knowledge base tree (folders + articles)","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:read"]}},"/api/v1/kb/folders":{"get":{"operationId":"V1KnowledgeBaseController_listFolders","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all KB folders (flat)","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:read"]},"post":{"operationId":"V1KnowledgeBaseController_createFolder","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateFolderDto"}}}},"responses":{"201":{"description":""},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Create a KB folder","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:write"]}},"/api/v1/kb/folders/{id}":{"get":{"operationId":"V1KnowledgeBaseController_getFolder","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Get folder by ID","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:read"]},"patch":{"description":"Only the fields present in the body are changed.","operationId":"V1KnowledgeBaseController_updateFolder","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateFolderDto"}}}},"responses":{"200":{"description":"The updated folder, as returned by GET /v1/kb/folders/:id"},"400":{"description":"`title` or `sort_order` is null, or another field is invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Update a KB folder","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:write"]},"delete":{"operationId":"V1KnowledgeBaseController_removeFolder","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"204":{"description":""},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Delete a KB folder (cascade)","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:write"]}},"/api/v1/kb/articles":{"get":{"operationId":"V1KnowledgeBaseController_listArticles","parameters":[{"name":"folder_id","required":false,"in":"query","description":"Filter by folder ID","schema":{"type":"number"}},{"name":"stream","required":false,"in":"query","description":"Filter by stream name/key/ID","schema":{"type":"string"}},{"name":"tag","required":false,"in":"query","description":"Filter by tag name (exact); an unknown tag yields an empty list","schema":{"type":"string"}},{"name":"search","required":false,"in":"query","description":"Full-text search","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}}],"responses":{"400":{"description":"Invalid limit or folder_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"List KB articles with filters","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:read"]},"post":{"operationId":"V1KnowledgeBaseController_createArticle","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1CreateArticleDto"}}}},"responses":{"201":{"description":"The created article, as returned by GET /v1/kb/articles/:id"},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Create a KB article","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:write"]}},"/api/v1/kb/articles/{id}":{"get":{"operationId":"V1KnowledgeBaseController_getArticle","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Get KB article by ID (with content and tags)","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:read"]},"patch":{"description":"Only the fields present in the body are changed.","operationId":"V1KnowledgeBaseController_updateArticle","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UpdateArticleDto"}}}},"responses":{"200":{"description":"The updated article, as returned by GET /v1/kb/articles/:id"},"400":{"description":"`title` or `is_pinned` is null, or another field is invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Update a KB article","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:write"]},"delete":{"operationId":"V1KnowledgeBaseController_removeArticle","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"204":{"description":""},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}}},"security":[{"bearer":[]}],"summary":"Delete a KB article","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:write"]}},"/api/v1/kb/search":{"get":{"operationId":"V1KnowledgeBaseController_searchArticles","parameters":[{"name":"q","required":false,"in":"query","description":"Search text (title and content). Optional: without it every article matches, narrowed by `stream` and `tag`","schema":{"type":"string"}},{"name":"stream","required":false,"in":"query","description":"Filter by stream name/key/ID","schema":{"type":"string"}},{"name":"tag","required":false,"in":"query","description":"Filter by tag name (exact); an unknown tag yields an empty result","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":200,"default":50,"type":"number"}}],"responses":{"200":{"description":"{ results: [...], total }"},"400":{"description":"Invalid limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"502":{"description":"The search engine rejected the query"},"503":{"description":"Search is temporarily unavailable — retry later"}},"security":[{"bearer":[]}],"summary":"Full-text search KB articles (OpenSearch with MySQL fallback)","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:read"]}},"/api/v1/kb/reindex":{"post":{"operationId":"V1KnowledgeBaseController_reindex","parameters":[],"responses":{"201":{"description":""}},"security":[{"bearer":[]}],"summary":"Reindex KB articles of the current user into OpenSearch (other users are not affected)","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:write"]}},"/api/v1/kb/tags":{"get":{"operationId":"V1KnowledgeBaseController_listTags","parameters":[],"responses":{"200":{"description":""}},"security":[{"bearer":[]}],"summary":"List all KB tags","tags":["API v1 — Knowledge Base"],"x-required-scopes":["kb:read"]}}},"info":{"title":"Mnogodel API v1","description":"## Authentication\n\nAll endpoints require a **Personal Access Token (PAT)**, except device pairing (`/v1/auth/device/*`), which issues a token to a device and works without one.\n\n1. Go to **Settings → API Keys** in your Mnogodel cabinet\n2. Create a token with the required scopes\n3. Pass it in the `Authorization` header: `Bearer mnd_...`\n\nToken prefix: `mnd_`. Each token has scoped permissions — a request to an endpoint without the required scope returns `403`.\n\n## Rate Limits\n\n`1000` requests per minute per token, all `/v1` endpoints together (fixed one-minute window). Every response past authentication and scope check carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (seconds until the window resets). Over the limit: `429` with a `Retry-After` header (seconds) and body `{ \"statusCode\": 429, \"error\": \"Too Many Requests\", \"message\": \"Rate limit exceeded\", \"retry_after_seconds\": N }`.\n\nDevice pairing (`/v1/auth/device/*`, no token) is limited per client IP instead: `/start` 10 per minute and 30 per 5 minutes, `/poll` 30 per minute and 150 per 5 minutes; over the limit — `429` with `Retry-After-short` / `Retry-After-medium`.\n\n## Resolving by name\n\nStreams, projects, and tags can be referenced by **name** or **numeric ID** — e.g. `\"stream\": \"Development\"` or `\"stream\": 1`.","version":"1.0","contact":{}},"tags":[],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http","description":"Personal Access Token (mnd_...)"}},"schemas":{"V1DeviceStartDto":{"type":"object","properties":{"device_name":{"type":"string","example":"Garmin Enduro 3","maxLength":64,"description":"Shown on the confirmation page and as the key name. Up to 64 characters (Unicode code points)"},"scopes":{"example":["tracker:read","tracker:write"],"description":"Requested scopes; intersected with the device-flow allowlist (tracker:read, tracker:write)","type":"array","items":{"type":"string"}}}},"V1DevicePollDto":{"type":"object","properties":{"device_code":{"type":"string","description":"The device_code returned by /start"}},"required":["device_code"]},"V1TrackerStateDto":{"type":"object","properties":{"running":{"type":"boolean","example":true,"description":"Whether a time entry is currently running"},"server_now":{"type":"number","example":1785309072,"description":"Server time, unix epoch seconds"},"entry_id":{"type":"number","example":12345,"description":"Active entry ID"},"activity_id":{"type":"number","example":67890,"description":"Current activity ID"},"label":{"type":"string","example":"Deep work","description":"Current activity label, truncated to 64 chars"},"tags":{"example":["Dev"],"description":"Tag names, at most 3","type":"array","items":{"type":"string"}},"elapsed_seconds":{"type":"number","example":3721,"description":"Seconds since the entry started"}},"required":["running","server_now"]},"V1TrackerToggleDto":{"type":"object","properties":{"expect":{"type":"string","enum":["running","stopped"],"description":"Desired end state. `running` starts only if nothing is running and never restarts a running timer; `stopped` stops only if something is running and returns 200 otherwise. Omit to flip the current state."},"label":{"type":"string","example":"Deep work","description":"Label for the activity when starting","maxLength":255},"tags":{"example":["Dev"],"description":"Tag names or IDs when starting. A numeric ID may be sent as a JSON number or a string","type":"array","items":{"type":"string"}},"description":{"type":"string","example":"Sprint planning","description":"Entry description: set on the new entry when starting, saved on the stopped entry when stopping; at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535}}},"V1TrackerToggleResultDto":{"type":"object","properties":{"running":{"type":"boolean","example":true,"description":"Whether a time entry is currently running"},"server_now":{"type":"number","example":1785309072,"description":"Server time, unix epoch seconds"},"entry_id":{"type":"number","example":12345,"description":"Active entry ID"},"activity_id":{"type":"number","example":67890,"description":"Current activity ID"},"label":{"type":"string","example":"Deep work","description":"Current activity label, truncated to 64 chars"},"tags":{"example":["Dev"],"description":"Tag names, at most 3","type":"array","items":{"type":"string"}},"elapsed_seconds":{"type":"number","example":3721,"description":"Seconds since the entry started"},"stopped_entry_id":{"type":"number","example":327,"description":"ID of the entry that was stopped by this call"},"stopped_duration_seconds":{"type":"number","example":3600,"description":"Duration of the stopped entry, seconds"}},"required":["running","server_now"]},"V1TrackerLabelDto":{"type":"object","properties":{"label":{"type":"string","example":"Code review","description":"New label; pass an empty string to clear it","maxLength":255},"tags":{"example":["Dev"],"description":"Tag names or IDs; replaces the current set. A numeric ID may be sent as a JSON number or a string","type":"array","items":{"type":"string"}},"activity_id":{"type":"number","example":601,"description":"Target activity; defaults to the current one. Must belong to the active entry."}}},"V1ActivityDto":{"type":"object","properties":{"title":{"type":"string","example":"Code review","description":"Activity title","maxLength":255},"tags":{"example":["Development","12"],"description":"Tag names or IDs to attach to this activity. A numeric ID may be sent as a JSON number or a string","type":"array","items":{"type":"string"}},"started_at":{"type":"string","example":"2026-05-14T10:00:00.000Z","description":"Activity start time (ISO 8601)"},"ended_at":{"type":"string","example":"2026-05-14T11:30:00.000Z","description":"Activity end time (ISO 8601)"}}},"V1TrackerStartDto":{"type":"object","properties":{"description":{"type":"string","example":"Working on API docs","description":"Entry description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"activities":{"description":"Activities to add immediately (replaces auto-created empty activity)","type":"array","items":{"$ref":"#/components/schemas/V1ActivityDto"}}}},"V1TrackerStopDto":{"type":"object","properties":{"description":{"type":"string","example":"Finished the feature","description":"Final description for the stopped entry, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"ended_at":{"type":"string","example":"2026-07-29T10:00:00.000Z","description":"Stop the entry retroactively at this moment (ISO 8601). Must be after the entry started and not in the future. Lets an offline-queue client close a hanging entry exactly where its offline work began, so entries never overlap."},"activities":{"description":"Activities for the new entry (only used if restart=true)","type":"array","items":{"$ref":"#/components/schemas/V1ActivityDto"}},"restart":{"type":"boolean","default":false,"description":"If true, immediately start a new entry after stopping"}}},"V1TrackerLogDto":{"type":"object","properties":{"started_at":{"type":"string","example":"2026-05-14T09:00:00.000Z","description":"Entry start time (ISO 8601)"},"ended_at":{"type":"string","example":"2026-05-14T12:00:00.000Z","description":"Entry end time (ISO 8601)"},"description":{"type":"string","example":"Retroactive entry","description":"Entry description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"activities":{"description":"Activities for this entry","type":"array","items":{"$ref":"#/components/schemas/V1ActivityDto"}}},"required":["started_at","ended_at"]},"V1TodayTaskDto":{"type":"object","properties":{"id":{"type":"number","example":428},"title":{"type":"string","example":"Поиск по Вин как у ABCP","description":"Truncated to 48 characters"},"tag":{"type":"string","example":"Omega","description":"Tracker tag matched by project, then by stream"}},"required":["id","title"]},"V1CreateTagDto":{"type":"object","properties":{"name":{"type":"string","example":"Deep work","description":"Tag name; an existing tag with the same name is returned as is. Surrounding spaces are ignored","minLength":1,"maxLength":100},"color":{"type":"string","example":"#76bb40","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"}},"required":["name"]},"V1CreateTaskDto":{"type":"object","properties":{"title":{"type":"string","example":"Implement login page","description":"Task title","minLength":1,"maxLength":500},"description":{"type":"string","example":"Add OAuth2 support with Google provider","description":"Task description (markdown), at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"stream":{"type":"string","example":"Development","description":"Stream name, key, or numeric ID. A numeric ID may be sent as a JSON number or a string"},"project":{"type":"string","example":"Website","description":"Project name or numeric ID. A numeric ID may be sent as a JSON number or a string"},"status":{"type":"string","example":"open","enum":["open","in_progress","completed","cancelled"],"default":"open","description":"`done` is accepted as a legacy alias of `completed`"},"priority":{"type":"string","example":"medium","enum":["low","medium","high","urgent"],"default":"medium"},"due_date":{"type":"string","nullable":true,"example":"2026-06-01","description":"Due date / deadline (YYYY-MM-DD). A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written."},"start_date_plan":{"type":"string","nullable":true,"example":"2026-05-25","description":"Planned start date (YYYY-MM-DD). For Gantt chart / B24 START_DATE_PLAN. A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written."},"end_date_plan":{"type":"string","nullable":true,"example":"2026-06-01","description":"Planned end date (YYYY-MM-DD). For Gantt chart / B24 END_DATE_PLAN. A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written."},"tags":{"type":"string","example":"frontend,auth","description":"Comma-separated tags, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"estimated_hours":{"type":"number","example":4,"description":"Estimated hours","minimum":0,"maximum":999999},"complexity":{"type":"string","example":"medium","enum":["trivial","easy","medium","hard"],"description":"Task complexity for energy-based scheduling"}},"required":["title","stream"]},"V1UpdateTaskDto":{"type":"object","properties":{"title":{"type":"string","example":"Updated title","minLength":1,"maxLength":500},"description":{"type":"string","example":"Updated description","description":"Task description (markdown), at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"status":{"type":"string","example":"in_progress","enum":["open","in_progress","completed","cancelled"],"description":"`done` is accepted as a legacy alias of `completed`"},"priority":{"type":"string","example":"high","enum":["low","medium","high","urgent"]},"due_date":{"type":"string","nullable":true,"example":"2026-06-15","description":"Due date / deadline (YYYY-MM-DD). A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written. \"\" or null clears."},"start_date_plan":{"type":"string","nullable":true,"example":"2026-06-10","description":"Planned start date (YYYY-MM-DD). A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written. \"\" or null clears."},"end_date_plan":{"type":"string","nullable":true,"example":"2026-06-15","description":"Planned end date (YYYY-MM-DD). A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written. \"\" or null clears."},"tags":{"type":"string","example":"frontend,refactor","description":"Comma-separated tags, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"estimated_hours":{"type":"number","example":8,"minimum":0,"maximum":999999},"actual_hours":{"type":"number","example":6,"description":"Actual hours spent","minimum":0,"maximum":999999},"complexity":{"type":"string","example":"hard","enum":["trivial","easy","medium","hard"],"description":"Task complexity for energy-based scheduling"}}},"V1CreateProjectDto":{"type":"object","properties":{"name":{"type":"string","example":"Website redesign","description":"Project name","minLength":1,"maxLength":255},"stream":{"type":"string","example":"Development","description":"Stream name, key, or numeric ID. A numeric ID may be sent as a JSON number or a string"},"description":{"type":"string","example":"Full redesign of the public website","description":"Project description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"priority":{"type":"string","example":"medium","enum":["low","medium","high","urgent"],"default":"medium"},"color":{"type":"string","example":"#3B82F6","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"},"ice_impact":{"type":"number","example":8,"description":"ICE scoring: Impact (1-10)","minimum":1,"maximum":10},"ice_confidence":{"type":"number","example":7,"description":"ICE scoring: Confidence (1-10)","minimum":1,"maximum":10},"ice_ease":{"type":"number","example":6,"description":"ICE scoring: Ease (1-10)","minimum":1,"maximum":10}},"required":["name","stream"]},"V1UpdateProjectDto":{"type":"object","properties":{"name":{"type":"string","example":"Updated name","minLength":1,"maxLength":255},"description":{"type":"string","example":"Updated description","description":"Project description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"status":{"type":"string","example":"active","enum":["planning","active","on_hold","completed","cancelled"],"description":"`paused` is accepted as an alias of `on_hold`"},"priority":{"type":"string","example":"high","enum":["low","medium","high","urgent"]},"color":{"type":"string","example":"#EF4444","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"},"ice_impact":{"type":"number","example":8,"description":"ICE: Impact (1-10)","minimum":1,"maximum":10},"ice_confidence":{"type":"number","example":7,"description":"ICE: Confidence (1-10)","minimum":1,"maximum":10},"ice_ease":{"type":"number","example":6,"description":"ICE: Ease (1-10)","minimum":1,"maximum":10}}},"V1CreateStreamDto":{"type":"object","properties":{"name":{"type":"string","example":"Development","description":"Stream name","minLength":1,"maxLength":255},"key":{"type":"string","example":"DEV","description":"Short key used in task IDs (DEV-42): 1-5 characters A-Z or 0-9, unique among your streams; lowercase letters are uppercased (`dev` → `DEV`)","pattern":"^[A-Za-z0-9]{1,5}$","maxLength":5},"description":{"type":"string","example":"Software development work","description":"Stream description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"color":{"type":"string","example":"#8B5CF6","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"}},"required":["name","key"]},"V1UpdateStreamDto":{"type":"object","properties":{"name":{"type":"string","example":"Updated name","minLength":1,"maxLength":255},"description":{"type":"string","example":"Updated description","description":"Stream description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"color":{"type":"string","example":"#EF4444","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"},"is_active":{"type":"boolean","example":false,"description":"Set to false to archive the stream"}}},"V1CreateNoteDto":{"type":"object","properties":{"title":{"type":"string","example":"Meeting notes","description":"Note title","maxLength":500},"content":{"type":"string","example":"# Summary\nDiscussed the roadmap...","description":"Note content (markdown)"},"stream":{"type":"string","example":"Development","description":"Stream name, key, or numeric ID. A numeric ID may be sent as a JSON number or a string"},"project":{"type":"string","example":"Website","description":"Project name or numeric ID. A numeric ID may be sent as a JSON number or a string"},"issue_id":{"type":"number","example":42,"description":"Link to a task by ID"}}},"V1UpdateNoteDto":{"type":"object","properties":{"title":{"type":"string","example":"Updated title","maxLength":500},"content":{"type":"string","example":"Updated content"}}},"V1CreateEventDto":{"type":"object","properties":{"title":{"type":"string","example":"Team meeting","minLength":1,"maxLength":500},"description":{"type":"string","description":"Event description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"start_date":{"type":"string","example":"2026-05-15T10:00:00.000Z","description":"Start. All-day event: a date `YYYY-MM-DD`. Timed event: ISO 8601 with `Z` or an offset; a date-time without an offset is wall-clock time in the user profile timezone (see `GET /v1/profile`), a bare date is its midnight. In responses an all-day event starts at `00:00:00Z` of its first day"},"end_date":{"type":"string","example":"2026-05-15T11:00:00.000Z","description":"End. All-day event: the date `YYYY-MM-DD` of the LAST day (inclusive), so a one-day event has `start_date` = `end_date`; a date-time is an exclusive boundary. In responses an all-day event ends at `00:00:00Z` of the day after its last day (exclusive, like iCalendar). Timed event: same formats as `start_date`"},"all_day":{"type":"boolean","default":false,"description":"All-day event. Its dates are calendar dates, not moments: no timezone is applied to them"},"calendar_id":{"type":"number","description":"Calendar ID. If omitted or `null`, the default calendar is used: the first visible calendar, otherwise the first one; if the user has no calendars, «Основной» is created. A calendar of another user gives 404."},"location":{"type":"string","maxLength":500},"color":{"type":"string","example":"#3B82F6","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"},"type":{"type":"string","enum":["meeting","task","reminder","deadline","personal","other"],"default":"meeting","description":"Event type, as in the cabinet calendar; `event` is accepted as an alias of `meeting`"}},"required":["title","start_date","end_date"]},"V1UpdateEventDto":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":500},"description":{"type":"string","description":"Event description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"start_date":{"type":"string","description":"Start. All-day event: a date `YYYY-MM-DD`. Timed event: ISO 8601 with `Z` or an offset; a date-time without an offset is wall-clock time in the user profile timezone (see `GET /v1/profile`), a bare date is its midnight. In responses an all-day event starts at `00:00:00Z` of its first day"},"end_date":{"type":"string","description":"End. All-day event: the date `YYYY-MM-DD` of the LAST day (inclusive), so a one-day event has `start_date` = `end_date`; a date-time is an exclusive boundary. In responses an all-day event ends at `00:00:00Z` of the day after its last day (exclusive, like iCalendar). Timed event: same formats as `start_date`"},"all_day":{"type":"boolean","description":"All-day event. Its dates are calendar dates, not moments: no timezone is applied to them. Switching it without dates converts the current dates in the user profile timezone"},"calendar_id":{"type":"number","description":"Calendar ID to move the event to (a calendar of another user gives 404). `null` does not detach the event: an event created in Mnogodel always belongs to a calendar, so `null` is ignored and the response keeps the current `calendar_id` (an event that has no calendar is put into the default one). Imported events (non-empty `external_source`) are controlled by their import: `calendar_id` is `null` for them and is not filled in."},"location":{"type":"string","maxLength":500},"color":{"type":"string","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"},"type":{"type":"string","enum":["meeting","task","reminder","deadline","personal","other"],"description":"Event type, as in the cabinet calendar; `event` is accepted as an alias of `meeting`"}}},"V1CreateHabitDto":{"type":"object","properties":{"name":{"type":"string","example":"Morning meditation","minLength":1,"maxLength":255},"description":{"type":"string","description":"Habit description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"frequency_count":{"type":"number","default":1,"minimum":1,"maximum":20,"description":"How many times per day"},"frequency_days":{"type":"array","description":"Days of week (0=Mon..6=Sun), each at most once; null or [] = every day","items":{"type":"integer","minimum":0,"maximum":6},"maxItems":7,"uniqueItems":true,"nullable":true},"target_days":{"type":"number","default":21,"minimum":1,"maximum":365,"description":"Target streak days"},"color":{"type":"string","example":"#10B981","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"},"reminder_time":{"type":"string","nullable":true,"example":"09:00","description":"Reminder time HH:MM; null — no reminder","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"planned":{"type":"boolean","default":false,"description":"Create as planned (not active)"}},"required":["name"]},"V1UpdateHabitDto":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string","description":"Habit description, at most 65535 bytes in UTF-8 (a Cyrillic letter takes 2 bytes)","maxLength":65535},"frequency_count":{"type":"number","minimum":1,"maximum":20,"description":"How many times per day"},"frequency_days":{"type":"array","description":"Days of week (0=Mon..6=Sun), each at most once; null or [] = every day","items":{"type":"integer","minimum":0,"maximum":6},"maxItems":7,"uniqueItems":true,"nullable":true},"target_days":{"type":"number","minimum":1,"maximum":365,"description":"Target streak days"},"color":{"type":"string","description":"Color #RRGGBB","pattern":"^#[0-9A-Fa-f]{6}$"},"reminder_time":{"type":"string","nullable":true,"example":"09:00","description":"Reminder time HH:MM; null removes the reminder","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"}}},"V1CreateFoodItemDto":{"type":"object","properties":{"name":{"type":"string","example":"Chicken breast","minLength":1,"maxLength":255},"brand":{"type":"string","maxLength":255},"calories_per_serving":{"type":"number","default":0,"minimum":0,"maximum":1000000},"protein_per_serving":{"type":"number","default":0,"minimum":0,"maximum":1000000},"fat_per_serving":{"type":"number","default":0,"minimum":0,"maximum":1000000},"carbs_per_serving":{"type":"number","default":0,"minimum":0,"maximum":1000000},"serving_size":{"type":"number","default":100,"exclusiveMinimum":true,"minimum":0,"maximum":1000000,"description":"Serving size, greater than 0"},"serving_unit":{"type":"string","default":"г","maxLength":20,"description":"Unit shown in the cabinet: \"г\" (grams, default), \"мл\", \"шт\" or \"порция\" (Cyrillic). Any other text up to 20 characters is stored as is"},"category":{"type":"string","maxLength":100}},"required":["name"]},"V1CreateFoodLogDto":{"type":"object","properties":{"meal_type_id":{"type":"number","description":"Meal type ID","minimum":1},"food_item_id":{"type":"number","description":"Food item ID (either this or recipe_id)","minimum":1},"recipe_id":{"type":"number","description":"Recipe ID (either this or food_item_id)","minimum":1},"date":{"type":"string","example":"2026-05-14","description":"Calendar day YYYY-MM-DD. Defaults to today in the user profile time zone (see GET /v1/profile)."},"portions":{"type":"number","default":1,"minimum":0.1,"maximum":1000000}},"required":["meal_type_id","portions"]},"V1CreateWaterLogDto":{"type":"object","properties":{"amount_ml":{"type":"number","example":250,"minimum":1,"maximum":10000,"description":"Whole milliliters"},"date":{"type":"string","example":"2026-05-14","description":"Calendar day YYYY-MM-DD. Defaults to today in the user profile time zone (see GET /v1/profile)."}},"required":["amount_ml"]},"V1UpdateGoalsDto":{"type":"object","properties":{"calories_target":{"type":"number","example":2100,"minimum":0,"maximum":100000,"description":"Daily calories, kcal (`calories` in GET)"},"protein_target":{"type":"number","example":120,"minimum":0,"maximum":100000,"description":"Daily protein, grams (`protein_g` in GET)"},"fat_target":{"type":"number","example":70,"minimum":0,"maximum":100000,"description":"Daily fat, grams (`fat_g` in GET)"},"carbs_target":{"type":"number","example":250,"minimum":0,"maximum":100000,"description":"Daily carbs, grams (`carbs_g` in GET)"},"water_target_ml":{"type":"number","example":2000,"minimum":0,"maximum":100000,"description":"Daily water, ml (`water_ml` in GET)"},"calories":{"type":"number","example":2100,"minimum":0,"maximum":100000,"description":"Same as `calories_target` (the name used by GET)"},"protein_g":{"type":"number","example":120,"minimum":0,"maximum":100000,"description":"Same as `protein_target` (the name used by GET)"},"fat_g":{"type":"number","example":70,"minimum":0,"maximum":100000,"description":"Same as `fat_target` (the name used by GET)"},"carbs_g":{"type":"number","example":250,"minimum":0,"maximum":100000,"description":"Same as `carbs_target` (the name used by GET)"},"water_ml":{"type":"number","example":2000,"minimum":0,"maximum":100000,"description":"Same as `water_target_ml` (the name used by GET)"}}},"V1CreateTransactionDto":{"type":"object","properties":{"account_id":{"type":"number","minimum":1},"amount":{"type":"number","description":"Amount in currency units (e.g. rubles, not kopecks), from -1000000000000 to 1000000000000","minimum":-1000000000000,"maximum":1000000000000},"type":{"type":"string","enum":["income","expense"],"description":"Only income and expense can be created or changed through the API; transfers and corrections are made in the cabinet (they are listed by GET /v1/finance/transactions)"},"date":{"type":"string","example":"2026-05-14","description":"Date YYYY-MM-DD. A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written."},"description":{"type":"string","maxLength":500},"category_id":{"type":"number","minimum":1},"currency":{"type":"string","default":"RUB","example":"RUB","pattern":"^[A-Za-z]{3}$","description":"3-letter ISO 4217 code, case-insensitive; must match the account currency"}},"required":["account_id","amount","type","date"]},"V1UpdateTransactionDto":{"type":"object","properties":{"amount":{"type":"number","description":"Amount in currency units (e.g. rubles, not kopecks), from -1000000000000 to 1000000000000","minimum":-1000000000000,"maximum":1000000000000},"type":{"type":"string","enum":["income","expense"],"description":"Only income and expense can be created or changed through the API; transfers and corrections are made in the cabinet (they are listed by GET /v1/finance/transactions)"},"date":{"type":"string","example":"2026-05-14","description":"Date YYYY-MM-DD. A date-time (ISO 8601) is also accepted and reduced to a date in the user profile timezone (GET /v1/profile): with Z or an offset — the day of that moment there; without an offset — the date as written."},"description":{"type":"string","maxLength":500},"category_id":{"type":"number","minimum":1},"currency":{"type":"string","example":"RUB","pattern":"^[A-Za-z]{3}$","description":"3-letter ISO 4217 code, case-insensitive; must match the account currency"}}},"EnergyLevelDto":{"type":"object","properties":{"start":{"type":"string","example":"09:00","pattern":"^(?:(?:[01]?\\d|2[0-3]):[0-5]\\d|24:00)$","description":"Start, HH:mm"},"end":{"type":"string","example":"12:00","pattern":"^(?:(?:[01]?\\d|2[0-3]):[0-5]\\d|24:00)$","description":"End, HH:mm ('24:00' is midnight); later than the start"}},"required":["start","end"]},"EnergyLevelsDto":{"type":"object","properties":{"high":{"description":"Morning hours for hard tasks","allOf":[{"$ref":"#/components/schemas/EnergyLevelDto"}]},"medium":{"$ref":"#/components/schemas/EnergyLevelDto"},"low":{"description":"Evening hours for easy tasks","allOf":[{"$ref":"#/components/schemas/EnergyLevelDto"}]}}},"UpdateSettingsDto":{"type":"object","properties":{"working_hours_start":{"type":"string","example":"09:00","description":"Working hours start (HH:mm) in the user's profile time zone; earlier than the end"},"working_hours_end":{"type":"string","example":"18:00","description":"Working hours end (HH:mm, '24:00' is midnight) in the user's profile time zone"},"work_schedule_type":{"type":"string","example":"weekdays","enum":["weekdays","everyday","custom"]},"work_days":{"example":[1,2,3,4,5],"description":"Work days (1=Mon..7=Sun), unique","type":"array","items":{"type":"number","maximum":7,"minimum":1}},"break_minutes":{"type":"number","example":15,"description":"Break between blocks (minutes)"},"max_block_minutes":{"type":"number","example":120,"description":"Max block duration (minutes)"},"energy_levels":{"nullable":true,"description":"Energy levels: wall time HH:mm in the user's profile time zone, each start earlier than its end. Each level is optional: a level missing from a non-empty object is not used by generate. null or an empty object {} resets to the defaults (GET returns null)","example":{"high":{"start":"09:00","end":"12:00"},"medium":{"start":"13:00","end":"16:00"},"low":{"start":"16:00","end":"18:00"}},"allOf":[{"$ref":"#/components/schemas/EnergyLevelsDto"}]}}},"GeneratePlanDto":{"type":"object","properties":{"stream_ids":{"example":[1,3],"description":"Stream IDs to include in planning","type":"array","items":{"type":"number"}},"calendar_ids":{"example":[1,2],"description":"Calendar IDs to check for occupied time","type":"array","items":{"type":"number"}},"date_from":{"type":"string","example":"2026-05-19","description":"Plan start date (YYYY-MM-DD) in the user's profile time zone. The period is at most 92 days, inclusive."},"date_to":{"type":"string","example":"2026-05-23","description":"Plan end date (YYYY-MM-DD, inclusive) in the user's profile time zone; not in the past"},"reset_existing":{"type":"boolean","example":false,"description":"Reset existing draft plans"}},"required":["stream_ids","calendar_ids","date_from","date_to"]},"V1CreateFolderDto":{"type":"object","properties":{"title":{"type":"string","example":"Programming","description":"Folder title","maxLength":500},"parent_id":{"type":"integer","nullable":true,"example":1,"description":"Parent folder ID (null = root)"},"icon":{"type":"string","nullable":true,"example":"📚","description":"Emoji icon","maxLength":10},"stream":{"type":"string","description":"Stream name, key, or numeric ID. A numeric ID may be sent as a JSON number or a string"},"sort_order":{"type":"number","minimum":-2147483648,"maximum":2147483647}},"required":["title"]},"V1UpdateFolderDto":{"type":"object","properties":{"title":{"type":"string","maxLength":500},"parent_id":{"type":"integer","nullable":true,"description":"Parent folder ID (null = root)"},"icon":{"type":"string","nullable":true,"description":"Emoji icon; null — remove","maxLength":10},"stream":{"type":"string","nullable":true,"description":"Stream name, key, or numeric ID; null — unlink from stream. A numeric ID may be sent as a JSON number or a string"},"sort_order":{"type":"number","minimum":-2147483648,"maximum":2147483647}}},"V1CreateArticleDto":{"type":"object","properties":{"title":{"type":"string","example":"How to use TypeORM migrations","description":"Article title","maxLength":500},"content":{"type":"string","nullable":true,"description":"Markdown content"},"folder_id":{"type":"integer","nullable":true,"description":"Folder ID (null = root)"},"stream":{"type":"string","description":"Stream name, key, or numeric ID. A numeric ID may be sent as a JSON number or a string"},"is_pinned":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string","maxLength":100},"description":"Tag names, each at most 100 characters (created if not exist)"}},"required":["title"]},"V1UpdateArticleDto":{"type":"object","properties":{"title":{"type":"string","maxLength":500},"content":{"type":"string","nullable":true,"description":"Markdown content; null — clear"},"folder_id":{"type":"integer","nullable":true,"description":"Folder ID (null = root)"},"stream":{"type":"string","nullable":true,"description":"Stream name, key, or numeric ID; null — unlink from stream. A numeric ID may be sent as a JSON number or a string"},"is_pinned":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string","maxLength":100},"description":"Tag names, each at most 100 characters (replaces all)"}}},"ValidationError":{"type":"object","description":"Error 400. Validation: `message` has one text per invalid field (the most important one) and `errors` breaks them down by field. A rejection by the service itself or by a path parameter (for example `ended_at must be after the entry started`, `Validation failed (numeric string is expected)`) has a single string in `message` and no `errors`","required":["statusCode","error","message"],"properties":{"statusCode":{"type":"integer","example":400},"error":{"type":"string","example":"Bad Request"},"message":{"oneOf":[{"type":"string","description":"A rejection by the service itself or by a path parameter: a single text, no `errors`","example":"ended_at must be after the entry started"},{"type":"array","items":{"type":"string"},"description":"Validation: one text per invalid field (the most important one), in the order of `errors`","example":["sort_order must be an integer number"]}],"description":"A string when the service itself or a path parameter rejects the request; an array of strings (one text per invalid field) when validation fails","example":["sort_order must be an integer number"]},"errors":{"type":"array","description":"Validation only: one entry per invalid field, in the order of `message` (at most 100)","items":{"type":"object","required":["field","code","message"],"properties":{"field":{"type":"string","example":"sort_order","description":"Path to the field, dot-separated: `title`, `ingredients.0.amount_g`"},"code":{"type":"string","example":"isInt","description":"Violated constraint: `isNotEmpty`, `isInt`, `maxLength`, `whitelistValidation`…"},"message":{"type":"string","example":"sort_order must be an integer number"}}}}}}}}}