# API Reference

All endpoints require login (session cookie or `Authorization: Bearer <api-key>`) unless marked **(public)**.

## Health

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/health` | Health check **(public)** |

## Auth

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/auth/status` | `{has_users, registration_open, oauth_enabled, local_auth_enabled}` **(public)** |
| POST | `/api/auth/register` | Register new user (first user becomes admin; 403 if registration closed or local auth disabled) |
| POST | `/api/auth/login` | Login with username/password (403 if local auth disabled) |
| POST | `/api/auth/logout` | Clear session |
| GET | `/api/auth/me` | Current user info (includes `has_password: bool`) |
| PUT | `/api/auth/password` | Change password `{current_password, new_password}` |
| PUT | `/api/auth/email` | Change email `{email, password?}` (password required only for local-auth users) |
| POST | `/api/auth/invalidate-sessions` | Bump `session_version` — evicts all other sessions, keeps current alive |
| POST | `/api/auth/forgot-password` | Send password reset email `{email}` |
| POST | `/api/auth/reset-password` | Reset password with token `{token, new_password}` |
| GET | `/api/auth/oauth/login` | Initiate OIDC PKCE flow → redirect to provider |
| GET | `/api/auth/oauth/callback` | OIDC callback — exchange code, find/create user, redirect to `/` |
| GET | `/api/auth/invitation/:token` | Validate invitation token **(public)** |
| POST | `/api/auth/register-with-invite` | Register with token `{token, username, password}` **(public)** |

## Notes

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/notes` | List notes. Params: `q`, `tag`, `sort`, `order`, `limit`, `offset`, `project_id`, `milestone_id`, `parent_id`, `type` (`note`/`task`/`all`) |
| POST | `/api/notes` | Create note `{title, body, tags?, status?, priority?, due_date?, project_id?, milestone_id?, parent_id?}` |
| GET | `/api/notes/tags` | All tags (param: `q` for filter) |
| POST | `/api/notes/suggest-tags` | LLM tag suggestions `{title, body, current_tags?}` → `{suggested_tags}` |
| POST | `/api/notes/link-suggestions` | Detect note titles as plain text in body `{body, project_id, exclude_note_id}` → `[{note_id, title, count}]` |
| GET | `/api/notes/by-title` | Resolve note by exact title (param: `title`) |
| POST | `/api/notes/resolve-title` | Get-or-create note by title `{title}` (wikilink click) |
| GET | `/api/notes/:id` | Get single note |
| PUT | `/api/notes/:id` | Full update |
| PATCH | `/api/notes/:id` | Partial update (same fields as PUT) |
| DELETE | `/api/notes/:id` | Delete note |
| POST | `/api/notes/:id/convert-to-task` | Set `status='todo'`, `priority='none'` |
| POST | `/api/notes/:id/convert-to-note` | Clear `status`, `priority`, `due_date` |
| POST | `/api/notes/:id/append-tag` | Add tag `{tag}` → updated note |
| GET | `/api/notes/:id/backlinks` | Notes/tasks with `[[Title]]` references to this note |
| GET | `/api/notes/:id/versions` | List note version history |
| GET | `/api/notes/:id/versions/:vid` | Get a specific version |
| GET | `/api/notes/:id/draft` | Get current AI draft |
| PUT | `/api/notes/:id/draft` | Save AI draft |
| DELETE | `/api/notes/:id/draft` | Delete AI draft |
| POST | `/api/notes/assist` | Launch AI assist generation → 202 `{body, target_section?, instruction, whole_doc?}` |
| GET | `/api/notes/assist/stream` | SSE stream for assist (Last-Event-ID reconnect; events: `chunk`, `done`, `error`) |

## Tasks

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/tasks` | List tasks. Params: `q`, `tag`, `status`, `priority`, `due_before`, `due_after`, `sort`, `order`, `limit`, `offset` |
| POST | `/api/tasks` | Create task (accepts `project` name string → resolved to `project_id`) |
| GET | `/api/tasks/:id` | Get task (includes `parent_title`) |
| PUT | `/api/tasks/:id` | Full update |
| PATCH | `/api/tasks/:id/status` | Quick status update `{status}` |
| DELETE | `/api/tasks/:id` | Delete task |
| GET | `/api/tasks/:id/logs` | List work logs |
| POST | `/api/tasks/:id/logs` | Create log `{content, duration_minutes?}` |
| PATCH | `/api/tasks/:id/logs/:log_id` | Update log |
| DELETE | `/api/tasks/:id/logs/:log_id` | Delete log |

## Projects & Milestones

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/projects` | List projects (owned + shared) |
| POST | `/api/projects` | Create project |
| GET | `/api/projects/:id` | Get project with `milestone_summary` |
| PATCH | `/api/projects/:id` | Update project |
| DELETE | `/api/projects/:id` | Delete project |
| GET | `/api/projects/:id/notes` | Notes + tasks in this project |
| GET | `/api/projects/:id/milestones` | List milestones |
| POST | `/api/projects/:id/milestones` | Create milestone |
| PATCH | `/api/projects/:id/milestones/:mid` | Update milestone |
| DELETE | `/api/projects/:id/milestones/:mid` | Delete milestone |

## Sharing

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/projects/:id/shares` | List project shares |
| POST | `/api/projects/:id/shares` | Create project share `{user_id?, group_id?, permission}` |
| PATCH | `/api/projects/:id/shares/:sid` | Update permission |
| DELETE | `/api/projects/:id/shares/:sid` | Remove share |
| GET | `/api/notes/:id/shares` | List note shares |
| POST | `/api/notes/:id/shares` | Create note share |
| PATCH | `/api/notes/:id/shares/:sid` | Update permission |
| DELETE | `/api/notes/:id/shares/:sid` | Remove share |
| GET | `/api/shared-with-me` | All resources shared with the current user |

## Groups

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/groups` | List all groups (admin only) |
| POST | `/api/groups` | Create group `{name, description?}` |
| PATCH | `/api/groups/:id` | Update group |
| DELETE | `/api/groups/:id` | Delete group |
| GET | `/api/groups/:id/members` | List members |
| POST | `/api/groups/:id/members` | Add member `{user_id, role}` |
| PATCH | `/api/groups/:id/members/:uid` | Update member role |
| DELETE | `/api/groups/:id/members/:uid` | Remove member |

## Chat

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/chat/conversations` | List conversations (params: `limit`, `offset`) |
| POST | `/api/chat/conversations` | Create conversation `{title?, model?}` |
| POST | `/api/chat/conversations/bulk-delete` | Delete multiple conversations `{ids: number[]}` |
| GET | `/api/chat/conversations/:id` | Get conversation with all messages |
| PATCH | `/api/chat/conversations/:id` | Update title or model |
| DELETE | `/api/chat/conversations/:id` | Delete conversation (cascades to messages) |
| POST | `/api/chat/conversations/:id/messages` | Start generation → 202. Body: `{content, context_note_id?, include_note_ids?, rag_project_id?, workspace_project_id?, think?}` |
| GET | `/api/chat/conversations/:id/generation/stream` | SSE stream (Last-Event-ID reconnect; events: `context`, `chunk`, `tool_call`, `status`, `done`, `error`) |
| POST | `/api/chat/conversations/:id/generation/cancel` | Cancel active generation |
| POST | `/api/chat/messages/:id/save-as-note` | Save assistant message as note |
| POST | `/api/chat/conversations/:id/summarize` | Summarize conversation → note |
| GET | `/api/chat/status` | Ollama availability + model state `{ollama, model, default_model}` |
| GET | `/api/chat/models` | List installed Ollama models (includes `loaded: bool`, `modified_at`) |
| POST | `/api/chat/models/pull` | Pull model (SSE NDJSON progress) `{model}` |
| POST | `/api/chat/models/delete` | Delete model `{model}` |
| GET | `/api/chat/ps` | Currently loaded (hot) models |
| POST | `/api/chat/warm` | Pre-load model into VRAM `{model}` → 202 |

## Quick Capture

| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/quick-capture` | Classify + create item from natural language `{text}` → `{success, type, message, data}` |

## Search

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/search` | Semantic + keyword search across notes and tasks. Params: `q`, `type` (`note`/`task`/`all`), `limit` |

## Journal

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/journal/config` | Get journal configuration (locations, temp_unit, prep schedule) |
| PUT | `/api/journal/config` | Save journal configuration; live-reschedules the prep job |
| GET | `/api/journal/today` | Get/create today's journal conversation + messages |
| GET | `/api/journal/day/:iso` | Get a specific day's journal conversation (read-only) |
| GET | `/api/journal/days` | List dates with journal content, newest first |
| POST | `/api/journal/trigger-prep` | Force-regenerate today's prep (or `{date}` for a specific day) |
| GET | `/api/journal/weather` | Cached weather rows; auto-refreshes stale rows in the background |
| GET | `/api/journal/weather/current` | Live current conditions for the primary configured location |
| POST | `/api/journal/weather/refresh` | Manual refresh of all configured locations |
| POST | `/api/journal/weather/geocode` | Geocode place name `{query}` → `{lat, lon, label}` |
| POST | `/api/journal/moments/:id/update` | Update a recorded moment |
| DELETE | `/api/journal/moments/:id` | Delete a moment |

## Settings

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/settings` | All settings as `{key: value}` |
| PUT | `/api/settings` | Update settings `{key: value, ...}` |
| GET | `/api/settings/models` | Installed models + defaults |
| GET | `/api/settings/search` | Proxy SearXNG search (params: `q`) |

## API Keys

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/api-keys` | List user's API keys |
| POST | `/api/api-keys` | Create key `{name, scope}` → `{key, ...}` (key shown once) |
| DELETE | `/api/api-keys/:id` | Revoke key |

## Fable MCP Distribution

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/fable-mcp/info` | `{available: bool, filename: string\|null}` |
| GET | `/api/fable-mcp/download` | Download wheel file |

## Notifications

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/notifications` | List notifications |
| GET | `/api/notifications/count` | Unread count |
| POST | `/api/notifications/:id/read` | Mark read |
| POST | `/api/notifications/read-all` | Mark all read |

## Push

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/push/vapid-public-key` | VAPID public key for subscription |
| POST | `/api/push/subscribe` | Register push subscription |
| DELETE | `/api/push/subscribe` | Unregister push subscription |

## Images

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/images/:id` | Serve cached image **(no auth required — IDs are opaque SHA-256)** |

## Users

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/users/search` | Search users by username/email prefix (param: `q`, min 2 chars, excludes self) |

## Export

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/export` | Export data. Params: `format=markdown` (ZIP with `.md` + YAML frontmatter) or `format=json` |

## Admin

| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/admin/backup` | Export backup (`?scope=user` for own data; full requires admin) |
| POST | `/api/admin/restore` | Restore from JSON backup |
| GET | `/api/admin/users` | List all users |
| DELETE | `/api/admin/users/:id` | Delete user (cannot delete self) |
| GET | `/api/admin/registration` | Get registration open/closed state |
| PUT | `/api/admin/registration` | Toggle registration `{open: bool}` |
| POST | `/api/admin/invitations` | Create invitation `{email}` → sends email |
| GET | `/api/admin/invitations` | List pending invitations |
| DELETE | `/api/admin/invitations/:id` | Revoke invitation |
| GET | `/api/admin/logs` | Log entries. Params: `category`, `user_id`, `search`, `date_from`, `date_to`, `limit`, `offset` |
| GET | `/api/admin/logs/stats` | Log category counts |
| GET | `/api/admin/base-url` | Get base URL setting |
| PUT | `/api/admin/base-url` | Set base URL `{base_url}` |
| GET | `/api/admin/smtp` | Get SMTP config (password masked) |
| PUT | `/api/admin/smtp` | Save SMTP config |
| POST | `/api/admin/smtp/test` | Send test email `{recipient}` |
