{"success":true,"data":{"version":"v1","base_url":"https://api.ralston.is","apps":["auth","feedback","tasks","meals","fitness","notes","security","settings","admin","budgeting","cpe","wardrobe","calendar","notifications","agents","uploads","economics","dev","hub"],"endpoint_count":372,"endpoints":[{"path":"/api/v1/auth/role","method":"GET","description":"Get the current user's role. Returns 'admin' or 'user'. Defaults to 'user' when no explicit role has been assigned.","outputSchema":{"type":"object","properties":{"role":{"type":"string","enum":["admin","user"],"description":"The user's role."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"auth"},{"path":"/api/v1/feedback/create","method":"POST","description":"Submit a feedback item (bug report or feature request). Automatically sets the submitter to the authenticated user and status to 'new'.","inputSchema":{"type":"object","required":["type","description","app"],"properties":{"type":{"type":"string","enum":["bug","feature_request"],"description":"The type of feedback."},"description":{"type":"string","description":"Detailed description of the bug or feature request."},"app":{"type":"string","description":"The app the feedback is about (e.g. 'tasks', 'budgeting', 'hub')."},"page_url":{"type":"string","description":"The full URL of the page where the feedback was submitted."},"route":{"type":"string","description":"The Next.js route path (e.g. '/tasks/[id]')."},"user_agent":{"type":"string","description":"Browser user agent string."},"viewport":{"type":"string","description":"Viewport dimensions (e.g. '1920x1080')."},"metadata":{"type":"object","description":"Additional context as key-value pairs."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"feedback"},{"path":"/api/v1/feedback/mine","method":"GET","description":"List the current user's own feedback items. Supports filtering by status, type, and product_id, with pagination.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["new","triaged","in_progress","resolved","wont_fix"],"description":"Filter by status."},"type":{"type":"string","enum":["bug","feature_request"],"description":"Filter by feedback type."},"product_id":{"type":"string","description":"Filter to feedback attributed to a specific product (dev.products.id). Nullable in source data — filter excludes unattributed feedback when set."},"limit":{"type":"integer","description":"Max items to return (default 50, max 100)."},"offset":{"type":"integer","description":"Offset for pagination."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array"},"count":{"type":"integer"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"feedback"},{"path":"/api/v1/feedback/list","method":"GET","description":"List all feedback items (dev.feedback_items) across all users. Admin only. Supports filtering by app, type, status, user_id, and product_id. Includes submitter display names.","inputSchema":{"type":"object","properties":{"app":{"type":"string","description":"Filter by app."},"type":{"type":"string","enum":["bug","feature_request"],"description":"Filter by feedback type."},"status":{"type":"string","enum":["new","triaged","in_progress","resolved","wont_fix"],"description":"Filter by status."},"user_id":{"type":"string","description":"Filter by submitter user ID."},"product_id":{"type":"string","description":"Filter to feedback attributed to a specific product (dev.products.id). Nullable in source data — filter excludes unattributed feedback when set."},"sort":{"type":"string","enum":["created_at","updated_at","status","type"],"description":"Sort field (default: created_at)."},"order":{"type":"string","enum":["asc","desc"],"description":"Sort order (default: desc)."},"limit":{"type":"integer","description":"Max items to return (default 50, max 100)."},"offset":{"type":"integer","description":"Offset for pagination."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array"},"count":{"type":"integer"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"admin","tier":"read","app":"feedback"},{"path":"/api/v1/feedback/:id","method":"GET","description":"Get full details of a feedback item including context, admin notes, and submitter name. Admin only.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"user_id":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"description":{"type":"string"},"app":{"type":"string"},"page_url":{"type":"string"},"route":{"type":"string"},"user_agent":{"type":"string"},"viewport":{"type":"string"},"metadata":{"type":"object"},"admin_notes":{"type":"string"},"submitter_name":{"type":"string"},"resolved_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"admin","tier":"read","app":"feedback"},{"path":"/api/v1/feedback/:id","method":"PATCH","description":"Update a feedback item. Admin only. Can change status and admin notes. Setting status to 'resolved' or 'wont_fix' auto-sets resolved_at.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["new","triaged","in_progress","resolved","wont_fix"],"description":"New status. Resolved/wont_fix auto-sets resolved_at."},"admin_notes":{"type":["string","null"],"description":"Admin notes. Set to null to clear."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"admin_notes":{"type":"string"},"resolved_at":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"admin","tier":"write","app":"feedback"},{"path":"/api/v1/feedback/:id","method":"DELETE","description":"Permanently delete a feedback item. Admin only. This cannot be undone.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"admin","tier":"write","app":"feedback"},{"path":"/api/v1/feedback/promote-to-bug","method":"POST","description":"Promote a feedback item to a bug report. Atomically creates a dev.bugs entry and links it to the feedback item. Admin only. The feedback item's status is set to 'triaged' and the bug starts at 'new' (requires triage for embedding/classification).","inputSchema":{"type":"object","required":["feedback_item_id"],"properties":{"feedback_item_id":{"type":"string","description":"UUID of the feedback item to promote."},"title_override":{"type":"string","description":"Override title (default: first 100 chars of description)."},"severity_override":{"type":"string","enum":["low","medium","high","critical"],"description":"Override severity (default: medium)."},"affected_app_override":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Override affected app (default: inferred from feedback's app field)."}}},"outputSchema":{"type":"object","properties":{"bug_id":{"type":"string"},"feedback_item_id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"admin","tier":"write","app":"feedback"},{"path":"/api/v1/feedback/promote-to-feature-idea","method":"POST","description":"Promote a feedback item to a feature idea. Atomically creates a dev.feature_ideas entry and links it to the feedback item. Admin only. The feedback item's status is set to 'triaged' and the feature idea starts at 'new' (requires triage for embedding/classification).","inputSchema":{"type":"object","required":["feedback_item_id"],"properties":{"feedback_item_id":{"type":"string","description":"UUID of the feedback item to promote."},"title_override":{"type":"string","description":"Override title (default: first 100 chars of description)."},"priority_override":{"type":"string","enum":["low","medium","high","critical"],"description":"Override priority (default: medium)."},"affected_app_override":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Override affected app (default: inferred from feedback's app field)."}}},"outputSchema":{"type":"object","properties":{"feature_idea_id":{"type":"string"},"feedback_item_id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"admin","tier":"write","app":"feedback"},{"path":"/api/v1/tasks/prompt-chain/create","method":"POST","description":"Create a chain of implementation prompts as a project. Supports both sequential (linear) and DAG (directed acyclic graph) dependency models. Each prompt becomes a task. In linear mode (default), completing one task unblocks the next. In DAG mode (when any prompt has depends_on), tasks can have multiple dependencies and run in parallel when all dependencies are satisfied. Each prompt can optionally be assigned to a specific agent.","inputSchema":{"type":"object","required":["project_name","prompts"],"properties":{"project_name":{"type":"string","description":"Name of the project. If a project with this name already exists, prompts are added to it."},"project_context":{"type":"string","description":"Optional persistent context included with every prompt in the chain (e.g. architecture notes, constraints)."},"idempotency_key":{"type":"string","maxLength":256,"description":"Optional key for safe retries. If the same key is sent again with the same body, the original response is replayed."},"prompts":{"type":"array","minItems":1,"items":{"type":"object","required":["name","prompt_content"],"properties":{"name":{"type":"string","description":"Short task title."},"description":{"type":"string","description":"Optional longer description."},"prompt_content":{"type":"string","description":"The full prompt text for Claude Code to execute."},"verification_items":{"type":"array","items":{"type":"string"},"description":"Checklist items that must all be checked before the task can be completed."},"assigned_agent_slug":{"type":"string","description":"Slug of the agent to assign this task to (e.g. 'db-architect', 'frontend-dev'). Optional."},"depends_on":{"type":"array","items":{"type":"string"},"description":"Array of task NAMES this task depends on (resolved to IDs during creation). Omit for linear chain behavior. Use empty array [] for no dependencies (immediately ready)."},"parallel_group":{"type":"string","description":"Group identifier for tasks that can run concurrently."},"estimated_tokens":{"type":"number","description":"Estimated token usage for this task."},"estimated_duration_minutes":{"type":"number","description":"Estimated wall clock time in minutes."},"max_retries":{"type":"number","description":"Maximum retry attempts before escalation. Defaults to 2."}}}},"chain_type":{"type":"string","enum":["feature","bugfix","refactor","infrastructure"],"description":"Type of work. Affects scoring (bugfix gets priority bias) and branch prefix. Defaults to 'feature'."},"chain_goal":{"type":"string","description":"Optional longer description of what this chain accomplishes."},"target_app":{"type":"string","description":"Which app this chain targets (e.g. 'tasks', 'meals'). Used for conflict avoidance — only one chain per target_app runs concurrently."},"priority":{"type":"number","description":"Explicit priority 0-3. Higher = more urgent. Defaults to 0."},"branch_prefix":{"type":"string","description":"Prefix for generated branch names (e.g. 'feature/', 'fix/'). Defaults based on chain_type."},"repo_url":{"type":"string","description":"Repository URL for this chain, if applicable."}}},"outputSchema":{"type":"object","properties":{"project_id":{"type":"string"},"project_url":{"type":"string"},"has_context":{"type":"boolean"},"chain_id":{"type":"string","description":"UUID of the created chain record."},"chain_status":{"type":"string","enum":["ready"],"description":"Chain status — always 'ready' on creation."},"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"url":{"type":"string"},"blocked_by":{"type":["string","null"]},"verification_count":{"type":"number"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/prompt-chain/:project_id","method":"GET","description":"Fetch a prompt chain with full task status, blocking information, and the next actionable task. Use when working through a prompt chain to find what to do next. Returns tasks in dependency order with prompt_content, verification_items, and execution_notes.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/projects","method":"GET","description":"List all projects that contain prompt chain tasks, showing progress and the next actionable task for each. Use to discover available prompt chains and their completion status. Supports optional repository_id and organization_id filters (only affects prompt_chain projects — general projects are unscoped). Projects in MCP-excluded contexts are filtered out.","inputSchema":{"type":"object","properties":{"type":{"type":"string","enum":["general","prompt_chain"],"description":"Optional filter by project type. If omitted, returns all types."},"include_archived":{"type":"boolean","description":"Include archived projects in results. Defaults to false."},"repository_id":{"type":"string","description":"Optional filter: only return prompt_chain projects whose repository_id matches. Ignored for general projects."},"organization_id":{"type":"string","description":"Optional filter: only return prompt_chain projects whose repository belongs to this organization. Ignored for general projects."}}},"outputSchema":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["general","prompt_chain"]},"has_context":{"type":"boolean"},"is_archived":{"type":"boolean"},"archived_at":{"type":["string","null"]},"task_count":{"type":"number"},"completed_count":{"type":"number"},"next_task":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/projects","method":"POST","description":"Create a new project for organizing related tasks. Use when you need to group tasks under a common goal or theme. Returns the project_id for use with tasks_create and tasks_update.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Project name."},"description":{"type":"string","description":"Project description/context."},"goal":{"type":"string","description":"Project goal."},"color":{"type":"string","description":"Display color."},"icon":{"type":"string","description":"Display icon."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/projects/:project_id","method":"GET","description":"Get a single project with its linked tasks and chain details. Returns task list with status and priority.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/projects/:project_id","method":"PATCH","description":"Update a project's name, description, goal, color, or icon. Only provided fields are changed.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"New project name."},"description":{"type":"string","description":"New description. Pass null to clear."},"goal":{"type":"string","description":"New goal."},"color":{"type":"string","description":"New color."},"icon":{"type":"string","description":"New icon."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/projects/:project_id/summary","method":"GET","description":"Get a project summary with task statistics and active task list. Returns total tasks, completed count, completion percentage, overdue count, and a list of active (non-completed, non-archived) tasks. Respects MCP context exclusions.","outputSchema":{"type":"object","properties":{"project":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"goal":{"type":"string"},"is_archived":{"type":"boolean"},"created_at":{"type":"string"}}},"stats":{"type":"object","properties":{"total":{"type":"number"},"completed":{"type":"number"},"completion_pct":{"type":"number"},"overdue":{"type":"number"}}},"active_tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"priority":{"type":"string"},"due_date":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/projects/:project_id/archive","method":"POST","description":"Archive a project. Archived projects are hidden from default views but preserved for history. Use to clean up completed or inactive projects.","outputSchema":{"type":"object","properties":{"project_id":{"type":"string"},"name":{"type":"string"},"is_archived":{"type":"boolean","enum":[true]},"archived_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/projects/:project_id/archive","method":"DELETE","description":"Unarchive a project. Restores an archived project to active visibility in default views.","outputSchema":{"type":"object","properties":{"project_id":{"type":"string"},"name":{"type":"string"},"is_archived":{"type":"boolean","enum":[false]},"archived_at":{"type":"null"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/:task_id/complete","method":"PATCH","description":"Mark a task as complete or report failure. For completion: all verification items must be checked first. For failure: set status='failed' with an optional failure_reason. Failed tasks retry automatically (up to max_retries), then escalate to a higher-model agent, and finally pause the chain and notify the human. Returns 404 for tasks in MCP-excluded contexts.","inputSchema":{"type":"object","properties":{"execution_notes":{"type":"string","description":"Optional summary of what was done during task execution."},"status":{"type":"string","enum":["completed","failed"],"description":"Task outcome. Defaults to 'completed'. Set to 'failed' to trigger retry/escalation logic."},"failure_reason":{"type":"string","description":"Why the task failed. Recorded in escalation_history for post-mortem analysis."}}},"outputSchema":{"type":"object","properties":{"task_id":{"type":"string"},"status":{"type":"string","enum":["completed","retry","escalated","failed"]},"execution_notes":{"type":["string","null"]},"failure_reason":{"type":["string","null"]},"retry_count":{"type":"number"},"max_retries":{"type":"number"},"escalated_from":{"type":["string","null"]},"escalated_to":{"type":["string","null"]},"unblocked_tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"new_status":{"type":"string"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/:task_id/retry-status","method":"GET","description":"Get retry and escalation history for a task. Shows current retry count, failure reason, and a chronological log of retry attempts and model escalations. Useful for diagnosing repeated failures and reviewing escalation decisions.","outputSchema":{"type":"object","properties":{"task_id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"retry_count":{"type":"number"},"max_retries":{"type":"number"},"failure_reason":{"type":["string","null"]},"assigned_agent_slug":{"type":["string","null"]},"claimed_by_agent_slug":{"type":["string","null"]},"blocked_reason":{"type":["string","null"]},"total_retries":{"type":"number"},"total_escalations":{"type":"number"},"escalation_history":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["retry","escalation"]},"timestamp":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/:task_id/verification","method":"PATCH","description":"Toggle a verification checklist item on a task. Each task may have verification items that must all be checked before the task can be marked complete. Use to confirm individual deliverables or acceptance criteria. Returns 404 for tasks in MCP-excluded contexts.","inputSchema":{"type":"object","required":["item_id","checked"],"properties":{"item_id":{"type":"string","description":"The ID of the verification item (e.g. \"v1\", \"v2\")."},"checked":{"type":"boolean","description":"Whether the item should be checked or unchecked."}}},"outputSchema":{"type":"object","properties":{"task_id":{"type":"string"},"item_id":{"type":"string"},"checked":{"type":"boolean"},"all_verified":{"type":"boolean"},"verification_progress":{"type":"string","description":"Progress string like \"3/5\"."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/create","method":"POST","description":"Create a new standalone task in the backlog. Use when you need to track a single action item outside of a prompt chain, such as a bug fix, follow-up, or ad-hoc task. Supports recurring tasks — set recurrence_frequency to create a task that auto-generates the next instance when completed. Rejects creation if project_id belongs to an MCP-excluded context.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Task title."},"description":{"type":"string","description":"Optional details."},"project_id":{"type":"string","description":"Optional project to link the task to."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Task priority. Defaults to medium."},"due_date":{"type":"string","format":"date","description":"Optional due date (YYYY-MM-DD). For recurring tasks, defaults to today if not set."},"status":{"type":"string","enum":["inbox","backlog","ready","in_progress"],"description":"Initial status. Defaults to backlog."},"recurrence_frequency":{"type":"string","enum":["daily","weekdays","weekly","monthly"],"description":"Set to make this a recurring task. When completed, the next instance is auto-generated."},"recurrence_days":{"type":"array","items":{"type":"number"},"description":"Days of week for 'weekdays' frequency (1=Mon, 7=Sun). Defaults to Mon-Fri."},"recurrence_end_date":{"type":"string","format":"date","description":"Optional end date for recurrence (YYYY-MM-DD). After this date, no more instances are generated."},"effort":{"type":"string","enum":["trivial","moderate","deep"],"description":"Cognitive effort level: trivial (autopilot), moderate (engaged), deep (sustained focus). Defaults to null (treated as moderate)."},"estimated_minutes":{"type":"number","description":"Estimated duration in minutes. Optional."},"assigned_to":{"type":"string","description":"UUID of a family member to assign this task to (from public.family_members)."}}},"outputSchema":{"type":"object","properties":{"task_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"url":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/chains/recommended","method":"GET","description":"Preview which chain the scoring engine would recommend next, without claiming it. Supports optional repository_id filter to scope to a single repo. For actual work assignment, use the next_chain tool instead.","inputSchema":{"type":"object","properties":{"repository_id":{"type":"string","description":"Optional filter: only consider chains whose primary_repository_id matches (dev.repositories.id)."}}},"outputSchema":{"type":"object","properties":{"chain_id":{"type":"string","description":"UUID of the recommended chain."},"chain_name":{"type":"string","description":"Human-readable name of the chain."},"chain_type":{"type":"string","enum":["feature","bugfix","refactor","infrastructure"],"description":"Type of work."},"chain_status":{"type":"string","description":"Always 'ready' for eligible chains."},"project_id":{"type":"string","description":"UUID of the parent project."},"project_name":{"type":"string","description":"Human-readable project name."},"target_app":{"type":["string","null"],"description":"Which app this chain targets (e.g. 'tasks', 'meals')."},"priority":{"type":"number","description":"Explicit priority 0-3."},"prompt_count":{"type":"number","description":"Total prompts in the chain."},"prompts_completed":{"type":"number","description":"Number of completed prompts."},"next_task":{"type":["object","null"],"description":"The next actionable task in the chain.","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"score":{"type":"number","description":"Composite score (0-105)."},"score_breakdown":{"type":"object","description":"Individual factor scores.","properties":{"chain_continuity":{"type":"number","description":"Progress bonus (max 40)."},"priority":{"type":"number","description":"Priority score (max 25)."},"dependency_unblocking":{"type":"number","description":"Downstream chains unblocked (max 15)."},"bug_bias":{"type":"number","description":"Bugfix bonus (max 10)."},"staleness":{"type":"number","description":"Days waiting bonus (max 10)."},"agent_availability":{"type":"number","description":"Bonus (5) when the chain's primary_repository_id has at least one eligible agent (globally applicable or bound)."}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/chains/recommended","method":"POST","description":"Score eligible chains and atomically claim the highest-scored one. Creates a dev_job and transitions the chain to in_progress. Returns null if no eligible chains. Supports optional repository_id filter to scope claim to a single repo. Use the next_chain MCP tool rather than calling this directly.","inputSchema":{"type":"object","properties":{"claimed_by":{"type":"string","description":"Identifier for the claiming Claude Code instance."},"repository_id":{"type":"string","description":"Optional filter: only consider chains whose primary_repository_id matches (dev.repositories.id)."}}},"outputSchema":{"type":"object","properties":{"chain_id":{"type":"string","description":"UUID of the claimed chain."},"chain_name":{"type":"string","description":"Human-readable name of the chain."},"chain_type":{"type":"string","enum":["feature","bugfix","refactor","infrastructure"],"description":"Type of work."},"chain_status":{"type":"string","description":"Chain status after claiming."},"project_id":{"type":"string","description":"UUID of the parent project."},"project_name":{"type":"string","description":"Human-readable project name."},"target_app":{"type":["string","null"],"description":"Which app this chain targets."},"priority":{"type":"number","description":"Explicit priority 0-3."},"prompt_count":{"type":"number","description":"Total prompts in the chain."},"prompts_completed":{"type":"number","description":"Number of completed prompts."},"next_task":{"type":["object","null"],"description":"The next actionable task in the chain.","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"score":{"type":"number","description":"Composite score (0-100)."},"score_breakdown":{"type":"object","description":"Individual factor scores.","properties":{"chain_continuity":{"type":"number","description":"Progress bonus (max 40)."},"priority":{"type":"number","description":"Priority score (max 25)."},"dependency_unblocking":{"type":"number","description":"Downstream chains unblocked (max 15)."},"bug_bias":{"type":"number","description":"Bugfix bonus (max 10)."},"staleness":{"type":"number","description":"Days waiting bonus (max 10)."}}},"dev_job_id":{"type":["string","null"],"description":"UUID of the created dev_job."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/chains/:chain_id/release","method":"POST","description":"Release a claimed chain back to the queue. Cancels the active dev_job and transitions the chain back to ready status.","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/today","method":"GET","description":"Get today's planned and day-anchored tasks. Returns tasks that are scheduled for today (start_date = due_date = today) and any currently in-progress tasks. Tasks in MCP-excluded contexts are filtered out. Each task includes time-slot fields (starts_at, ends_at, time_block) from task_plans if scheduled to a specific time. Use to see the daily work plan.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/contexts","method":"GET","description":"List all contexts (GTD-style: Work, Home, Errands, etc.) for the authenticated user. Use to discover available contexts before setting MCP exclusions.","outputSchema":{"type":"object","properties":{"contexts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"icon":{"type":["string","null"]},"color":{"type":["string","null"]},"is_active":{"type":"boolean"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/settings/mcp-exclusions","method":"GET","description":"View which contexts are currently excluded from MCP task tool results. Tasks and projects belonging to excluded contexts are hidden from all MCP task queries.","outputSchema":{"type":"object","properties":{"excluded_context_ids":{"type":"array","items":{"type":"string"},"description":"Array of excluded context UUIDs."},"excluded_contexts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"description":"Excluded contexts with resolved names."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/settings/mcp-exclusions","method":"POST","description":"Update the list of contexts excluded from MCP task tool results. Pass the full list of context IDs to exclude — this replaces any previous exclusions. Use tasks_contexts to list available contexts first.","inputSchema":{"type":"object","required":["context_ids"],"properties":{"context_ids":{"type":"array","items":{"type":"string"},"description":"Array of context UUIDs to exclude. Pass an empty array to clear all exclusions."}}},"outputSchema":{"type":"object","properties":{"excluded_context_ids":{"type":"array","items":{"type":"string"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/list","method":"GET","description":"List and filter tasks with flexible query parameters. Supports filtering by status, priority, project, context, due date, and text search. Results are paginated and exclude tasks in MCP-excluded contexts. Examples: 'Show all high-priority backlog tasks' → status=backlog&priority=high. 'What tasks are overdue?' → is_overdue=true. 'List tasks in project X' → project_id=<uuid>.","inputSchema":{"type":"object","properties":{"status":{"type":"string","description":"Filter by status. Single value or comma-separated: 'backlog', 'planned', 'in_progress', 'completed', 'blocked', 'archived'. Example: 'backlog,in_progress'."},"priority":{"type":"string","description":"Filter by priority. Single value or comma-separated: 'low', 'medium', 'high', 'urgent'. Example: 'high,urgent'."},"project_id":{"type":"string","description":"Filter to tasks in a specific project (UUID)."},"context_id":{"type":"string","description":"Filter to tasks in a specific context (UUID)."},"tag_id":{"type":"string","description":"Filter to tasks with a specific tag (UUID)."},"tag":{"type":"string","description":"Filter to tasks with a tag by name (case-insensitive). Alternative to tag_id when you know the tag name but not the UUID."},"due_before":{"type":"string","format":"date","description":"Tasks due on or before this date (YYYY-MM-DD)."},"due_after":{"type":"string","format":"date","description":"Tasks due on or after this date (YYYY-MM-DD)."},"is_overdue":{"type":"string","enum":["true","false"],"description":"If 'true', returns only tasks past their due date that are not completed or archived."},"search":{"type":"string","description":"Text search on task name and description (case-insensitive)."},"domain":{"type":"string","enum":["personal","family","dev"],"description":"Filter by task domain. 'personal' = personal tasks, 'family' = family routines/chores, 'dev' = development/prompt chain tasks. If omitted, returns all domains."},"sort":{"type":"string","enum":["created_at","updated_at","due_date","priority","title"],"description":"Sort field. Default: created_at."},"order":{"type":"string","enum":["asc","desc"],"description":"Sort order. Default: desc."},"limit":{"type":"string","description":"Max results to return (1-100, default 50)."},"offset":{"type":"string","description":"Pagination offset (default 0)."}}},"outputSchema":{"type":"object","properties":{"tasks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"priority":{"type":"string"},"effort":{"type":["string","null"],"enum":["trivial","moderate","deep",null],"description":"Cognitive effort level, or null."},"due_date":{"type":["string","null"]},"project":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"contexts":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"color":{"type":["string","null"]}}},"description":"Tags assigned to this task."},"verification":{"type":["object","null"],"properties":{"total":{"type":"number"},"checked":{"type":"number"}}}}}},"total":{"type":"number","description":"Total matching tasks."},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/tags","method":"GET","description":"List all tags for the authenticated user with task counts. Use to discover available tags for filtering tasks (via tag_id or tag query params on tasks/list).","outputSchema":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"color":{"type":["string","null"]},"task_count":{"type":"number","description":"Number of tasks with this tag."},"created_at":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/:task_id","method":"GET","description":"Get full details for a single task by ID. Returns all fields including description, verification checklist with checked status, execution notes (for prompt chain tasks), project info, context names, and subtask count. Returns 404 if the task is in an MCP-excluded context.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":"string"},"priority":{"type":"string"},"effort":{"type":["string","null"],"enum":["trivial","moderate","deep",null],"description":"Cognitive effort level, or null."},"due_date":{"type":["string","null"]},"start_date":{"type":["string","null"]},"completed_at":{"type":["string","null"]},"is_blocked":{"type":"boolean"},"blocked_reason":{"type":["string","null"]},"is_prompt_task":{"type":"boolean"},"prompt_content":{"type":["string","null"]},"verification_items":{"type":["array","null"],"items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"checked":{"type":"boolean"}}}},"execution_notes":{"type":["string","null"]},"project":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"contexts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"subtask_count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/:task_id","method":"PATCH","description":"Update an existing task. Only provided fields are changed (true PATCH semantics). Can modify name, description, status, priority, dates, project, contexts, prompt_content, and verification_items. Returns 404 for tasks in MCP-excluded contexts. Status values: backlog, planned, in_progress, completed, blocked, archived. Priority values: low, medium, high, urgent. Pass null to clear due_date, start_date, priority, description, or project_id. Pass context_ids as an array to replace all context assignments. prompt_content and verification_items can only be edited on prompt chain tasks with status 'backlog' or 'blocked' — tasks that have started execution are immutable for traceability.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"New task title."},"description":{"type":["string","null"],"description":"New description, or null to clear."},"status":{"type":"string","enum":["backlog","planned","in_progress","completed","blocked","archived"],"description":"New status."},"priority":{"type":["string","null"],"enum":["low","medium","high","urgent",null],"description":"New priority, or null to clear."},"effort":{"type":["string","null"],"enum":["trivial","moderate","deep",null],"description":"Cognitive effort level: trivial (autopilot), moderate (engaged), deep (sustained focus). Null to clear."},"estimated_minutes":{"type":["number","null"],"description":"Estimated duration in minutes, or null to clear."},"due_date":{"type":["string","null"],"description":"Due date (YYYY-MM-DD) or null to clear."},"start_date":{"type":["string","null"],"description":"Start date (YYYY-MM-DD) or null to clear."},"project_id":{"type":["string","null"],"description":"Move to a different project (UUID) or null to remove from project."},"context_ids":{"type":"array","items":{"type":"string"},"description":"Replace all context assignments. Pass empty array to remove all contexts."},"prompt_content":{"type":"string","description":"Replace prompt content on a prompt chain task. Only editable when task status is 'backlog' or 'blocked'. Full replace semantics."},"verification_items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"checked":{"type":"boolean"}},"required":["id","label","checked"]},"description":"Replace verification items on a prompt chain task. Only editable when task status is 'backlog' or 'blocked'. Full replace semantics — the provided array replaces the existing one entirely."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"priority":{"type":["string","null"]},"effort":{"type":["string","null"]},"estimated_minutes":{"type":["number","null"]},"due_date":{"type":["string","null"]},"project":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"contexts":{"type":"array","items":{"type":"string"}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/:task_id/archive","method":"POST","description":"Archive (soft-delete) a task. Sets status to 'archived'. The task can be restored later by updating its status back to another value (e.g. backlog). This is a convenience wrapper — equivalent to calling tasks_update with status='archived'.","outputSchema":{"type":"object","properties":{"task_id":{"type":"string"},"name":{"type":"string"},"previous_status":{"type":"string"},"status":{"type":"string","enum":["archived"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/schedule","method":"GET","description":"View the task schedule for a date range. Returns tasks grouped by date, plus overdue and optionally unscheduled tasks. Each task includes time-slot fields (starts_at, ends_at, time_block) from task_plans if scheduled to a specific time. Common uses: 'What does my week look like?' → start_date=2025-02-24&end_date=2025-02-28. 'What's scheduled for tomorrow?' → start_date=tomorrow&end_date=tomorrow. Overdue tasks (past due, not completed) are included by default.","inputSchema":{"type":"object","required":["start_date","end_date"],"properties":{"start_date":{"type":"string","format":"date","description":"Start of date range (YYYY-MM-DD, required)."},"end_date":{"type":"string","format":"date","description":"End of date range (YYYY-MM-DD, required)."},"include_overdue":{"type":"string","enum":["true","false"],"description":"Include overdue tasks from before start_date. Default: true."},"include_unscheduled":{"type":"string","enum":["true","false"],"description":"Include in_progress/planned/backlog tasks with no due date. Default: false."}}},"outputSchema":{"type":"object","properties":{"start_date":{"type":"string"},"end_date":{"type":"string"},"dates":{"type":"object","description":"Tasks grouped by date (YYYY-MM-DD keys). Empty dates are included.","additionalProperties":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"priority":{"type":"string"},"due_date":{"type":["string","null"]},"starts_at":{"type":["string","null"]},"ends_at":{"type":["string","null"]},"time_block":{"type":["string","null"]}}}}},"overdue":{"type":"array","description":"Tasks past their due date that are not completed."},"unscheduled":{"type":"array","description":"Active tasks with no due date (if requested)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"tasks"},{"path":"/api/v1/tasks/:task_id/schedule","method":"POST","description":"Schedule or unschedule a task. Three modes: (1) Day-anchored: pass { date: 'YYYY-MM-DD' } to set both start_date and due_date to the same date — this is how the Tasks app represents 'scheduled for a specific day'. (2) Date range: pass { start_date: '...', due_date: '...' } for multi-day tasks. (3) Unschedule: pass { unschedule: true } to clear all dates. Optionally include starts_at and ends_at (HH:MM) to assign the task to a specific time slot on the scheduled date.","inputSchema":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"Day-anchored scheduling: sets start_date = due_date = this date (YYYY-MM-DD)."},"start_date":{"type":["string","null"],"description":"Start date for range scheduling (YYYY-MM-DD)."},"due_date":{"type":["string","null"],"description":"Due date for range scheduling (YYYY-MM-DD)."},"unschedule":{"type":"boolean","description":"Set to true to clear all dates (unschedule the task)."},"starts_at":{"type":"string","description":"Optional time-slot start (HH:MM, e.g. '09:00'). Must be paired with ends_at. Creates a task_plans row for the scheduled date."},"ends_at":{"type":"string","description":"Optional time-slot end (HH:MM, e.g. '10:30'). Must be paired with starts_at."}}},"outputSchema":{"type":"object","properties":{"task_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"start_date":{"type":["string","null"]},"due_date":{"type":["string","null"]},"is_day_anchored":{"type":"boolean"},"unscheduled":{"type":"boolean"},"starts_at":{"type":["string","null"]},"ends_at":{"type":["string","null"]},"time_block":{"type":["string","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/bulk-archive","method":"POST","description":"Archive multiple tasks at once. Use during cleanup sessions to archive completed or stale tasks in bulk. Accepts up to 50 task IDs per call. Respects MCP context exclusions.","inputSchema":{"type":"object","required":["task_ids"],"properties":{"task_ids":{"type":"array","items":{"type":"string"},"description":"Array of task UUIDs to archive (max 50)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/tasks/bulk-update","method":"POST","description":"Update multiple tasks at once with the same field values. Use to batch-set status, priority, due dates, effort, estimated_minutes, or move tasks to a project. Accepts up to 50 task IDs per call. Respects MCP context exclusions.","inputSchema":{"type":"object","required":["task_ids"],"properties":{"task_ids":{"type":"array","items":{"type":"string"},"description":"Array of task UUIDs to update (max 50)."},"status":{"type":"string","description":"Set status on all tasks."},"priority":{"type":"string","description":"Set priority on all tasks."},"due_date":{"type":"string","description":"Set due_date (YYYY-MM-DD). Pass null to clear."},"start_date":{"type":"string","description":"Set start_date (YYYY-MM-DD). Pass null to clear."},"project_id":{"type":"string","description":"Move all tasks to this project. Pass null to remove."},"effort":{"type":["string","null"],"enum":["trivial","moderate","deep",null],"description":"Set effort level on all tasks, or null to clear."},"estimated_minutes":{"type":["number","null"],"description":"Set estimated time in minutes on all tasks, or null to clear."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"tasks"},{"path":"/api/v1/meals/recipes/create","method":"POST","description":"Create a new recipe with ingredients, steps, and optional tags. Use when a user shares a recipe in conversation and wants to save it to their recipe library. Handles ingredient deduplication automatically — if an ingredient already exists in the family's library, it will be reused rather than creating a duplicate.","inputSchema":{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"Recipe name."},"description":{"type":"string","description":"Brief description of the dish."},"servings":{"type":"number","default":4,"description":"Number of servings the recipe yields."},"prep_time_minutes":{"type":"number","description":"Preparation time in minutes."},"cook_time_minutes":{"type":"number","description":"Cooking time in minutes."},"source":{"type":"string","description":"Where the recipe came from (URL, book, etc.)."},"image_url":{"type":"string","description":"Relative storage path in the recipe-images private bucket (upload via meals_recipes_image first, then pass the returned storage_path here)."},"tags":{"type":"array","items":{"type":"string"},"description":"Tags for categorization (e.g. 'pasta', 'quick', 'vegetarian')."},"ingredients":{"type":"array","items":{"type":"object","required":["name","quantity","unit"],"properties":{"name":{"type":"string","description":"Ingredient name."},"quantity":{"type":"number","description":"Amount needed."},"unit":{"type":"string","description":"Unit of measurement (g, ml, cup, tbsp, etc.)."},"notes":{"type":"string","description":"Preparation notes (e.g. 'diced', 'room temperature')."},"category":{"type":"string","description":"Ingredient category (e.g. 'Produce', 'Dairy', 'Meat')."}}},"description":"List of ingredients with quantities."},"steps":{"type":"array","items":{"type":"object","required":["instruction"],"properties":{"instruction":{"type":"string","description":"Step instruction text."}}},"description":"Ordered cooking instructions."}}},"outputSchema":{"type":"object","properties":{"recipe_id":{"type":"string"},"title":{"type":"string"},"url":{"type":"string","description":"Direct link to the recipe in the Meals app."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/recipes","method":"GET","description":"Search and list recipes from the user's recipe library. Supports text search on title, filtering by favorites, and filtering by tag. Use to find recipes when planning meals or answering questions about what's in the library.","inputSchema":{"type":"object","properties":{"search":{"type":"string","description":"Text search on recipe title (case-insensitive partial match)."},"favorites_only":{"type":"boolean","description":"If true, only return favorited recipes."},"tag":{"type":"string","description":"Filter by tag (exact match)."},"include_cost":{"type":"boolean","description":"If true, include cost estimates (total_cost, cost_per_serving) for each recipe using ingredient pricing data."}}},"outputSchema":{"type":"object","properties":{"recipes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"servings":{"type":"number"},"prep_time_minutes":{"type":["number","null"]},"cook_time_minutes":{"type":["number","null"]},"is_favorite":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"active_version_number":{"type":["number","null"],"description":"Version number of the currently active recipe version. Null if no versions exist."},"family_rating":{"type":["number","null"],"description":"Average rating across all family members for all times this recipe was cooked. Null if no ratings."},"would_eat_again_pct":{"type":["number","null"],"description":"Percentage of 'would eat again' responses across all family members. Null if no responses."},"total_cost":{"type":["number","null"],"description":"Total estimated cost in ISK (only when include_cost=true)."},"cost_per_serving":{"type":["number","null"],"description":"Cost per serving in ISK (only when include_cost=true)."},"priced_ingredient_count":{"type":["number","null"],"description":"Number of ingredients with pricing data (only when include_cost=true)."},"unpriced_ingredient_count":{"type":["number","null"],"description":"Number of ingredients without pricing data (only when include_cost=true)."}}}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id/feedback-summary","method":"GET","description":"Get aggregated feedback for a recipe across all times it was cooked. Shows per-person breakdown (average rating, consumption habits, would-eat-again percentage) and overall totals. Optionally filter by version or compare feedback across all versions. Use to answer 'How does the family feel about this recipe?' or to compare recipe versions.","inputSchema":{"type":"object","properties":{"version_id":{"type":"string","description":"Optional version UUID to filter feedback to a specific recipe version only."},"compare_versions":{"type":"boolean","description":"If true, return feedback grouped by version with per-version breakdowns instead of aggregate."}}},"outputSchema":{"type":"object","properties":{"recipe_id":{"type":"string"},"recipe_title":{"type":"string"},"times_cooked":{"type":"number","description":"Number of distinct meal plans that used this recipe."},"per_person":{"type":"array","items":{"type":"object","properties":{"person_id":{"type":"string"},"person_name":{"type":"string"},"average_rating":{"type":["number","null"],"description":"Average star rating (1 decimal place)."},"would_eat_again_pct":{"type":["number","null"],"description":"Percentage of times they said yes to would_eat_again."},"consumption_breakdown":{"type":"object","properties":{"ate_well":{"type":"number"},"ate_some":{"type":"number"},"didnt_eat":{"type":"number"}}},"feedback_count":{"type":"number"}}}},"overall":{"type":["object","null"],"properties":{"average_rating":{"type":["number","null"]},"would_eat_again_pct":{"type":["number","null"]},"consumption_breakdown":{"type":"object","properties":{"ate_well":{"type":"number"},"ate_some":{"type":"number"},"didnt_eat":{"type":"number"}}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id","method":"GET","description":"Get full recipe details including ingredients with nutrition data, step-by-step instructions, tags, version history, and a calculated nutrition summary per serving. By default returns the active version's ingredients and steps. Pass version_id to view a specific version.","inputSchema":{"type":"object","properties":{"version_id":{"type":"string","description":"Optional UUID of a specific version to view. If omitted, the active version is used."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"servings":{"type":"number"},"prep_time_minutes":{"type":["number","null"]},"cook_time_minutes":{"type":["number","null"]},"total_time_minutes":{"type":["number","null"]},"source":{"type":["string","null"]},"is_favorite":{"type":"boolean"},"image_url":{"type":["string","null"],"description":"Relative storage path in the recipe-images private bucket, or null if no image. Use image_signed_url for display."},"image_signed_url":{"type":["string","null"],"description":"Time-limited signed URL for displaying the image (valid for 1 hour), or null if no image."},"tags":{"type":"array","items":{"type":"string"}},"steps":{"type":"array","items":{"type":"object","properties":{"step_number":{"type":"number"},"instruction":{"type":"string"}}}},"ingredients":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"notes":{"type":["string","null"]},"category":{"type":["string","null"]}}}},"nutrition":{"type":"object","description":"Calculated nutrition per serving (when ingredient data is available).","properties":{"calories":{"type":"number"},"protein_g":{"type":"number"},"carbs_g":{"type":"number"},"fat_g":{"type":"number"},"fiber_g":{"type":"number"}}},"cost":{"type":["object","null"],"description":"Cost estimate in ISK based on ingredient pricing data.","properties":{"total_cost":{"type":"number","description":"Total recipe cost in ISK."},"cost_per_serving":{"type":"number","description":"Per-serving cost in ISK."},"priced_ingredient_count":{"type":"number"},"unpriced_ingredient_count":{"type":["number","null"]}}},"active_version":{"type":["object","null"],"description":"The currently active version (or the requested version if version_id was passed).","properties":{"id":{"type":"string"},"version_number":{"type":"number"},"change_notes":{"type":["string","null"]},"created_at":{"type":"string"}}},"versions":{"type":"array","description":"Summary of all versions for this recipe, newest first.","items":{"type":"object","properties":{"id":{"type":"string"},"version_number":{"type":"number"},"change_notes":{"type":["string","null"]},"created_at":{"type":"string"}}}},"url":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id/versions/:version_id","method":"GET","description":"Get full details of a specific recipe version including resolved ingredient names and step-by-step instructions. Use to compare versions or view historical recipe data.","outputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Version UUID."},"recipe_id":{"type":"string"},"recipe_title":{"type":"string"},"version_number":{"type":"number"},"servings":{"type":"number"},"prep_time_minutes":{"type":["number","null"]},"cook_time_minutes":{"type":["number","null"]},"change_notes":{"type":["string","null"]},"created_at":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"step_number":{"type":"number"},"instruction":{"type":"string"}}}},"ingredients":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"display_order":{"type":"number"},"category":{"type":["string","null"]}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id/versions","method":"POST","description":"Create a new version of a recipe with updated ingredients, steps, and/or timing. The new version becomes the active version automatically. Use when a user wants to modify a recipe while preserving its history. The version number auto-increments.","inputSchema":{"type":"object","properties":{"ingredients":{"type":"array","description":"Full replacement ingredient list for this version.","items":{"type":"object","properties":{"name":{"type":"string","description":"Ingredient name (will be matched or created in the family library)."},"quantity":{"type":"number"},"unit":{"type":"string"},"category":{"type":"string","description":"Optional ingredient category."}},"required":["name","quantity","unit"]}},"steps":{"type":"array","description":"Full replacement step list for this version.","items":{"type":"object","properties":{"instruction":{"type":"string"}},"required":["instruction"]}},"servings":{"type":"number","description":"Servings for this version. Defaults to current recipe servings."},"prep_time_minutes":{"type":"number","description":"Prep time for this version."},"cook_time_minutes":{"type":"number","description":"Cook time for this version."},"change_notes":{"type":"string","description":"Description of what changed in this version."}}},"outputSchema":{"type":"object","properties":{"version_id":{"type":"string"},"version_number":{"type":"number"},"recipe_id":{"type":"string"},"change_notes":{"type":["string","null"]},"created_at":{"type":"string"},"ingredients_count":{"type":"number"},"steps_count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id/rollback","method":"POST","description":"Roll back a recipe to a previous version. Sets the specified version as the active version without deleting any newer versions. Use when a user wants to revert recipe changes.","inputSchema":{"type":"object","properties":{"version_id":{"type":"string","description":"UUID of the version to roll back to."}},"required":["version_id"]},"outputSchema":{"type":"object","properties":{"recipe_id":{"type":"string"},"active_version_id":{"type":"string"},"active_version_number":{"type":"number"},"change_notes":{"type":["string","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id","method":"PATCH","description":"Update an existing recipe. All fields are optional — only the fields you provide will be updated. If 'ingredients', 'steps', or 'tags' are provided, they fully replace the existing set. Returns the full updated recipe with nutrition and cost data. Use when a user wants to edit a recipe's title, description, timing, or replace its ingredients/steps/tags.","inputSchema":{"type":"object","properties":{"title":{"type":"string","description":"Updated recipe name."},"description":{"type":["string","null"],"description":"Updated description of the dish."},"servings":{"type":["number","null"],"description":"Updated number of servings."},"prep_time_minutes":{"type":["number","null"],"description":"Updated preparation time in minutes."},"cook_time_minutes":{"type":["number","null"],"description":"Updated cooking time in minutes."},"source":{"type":["string","null"],"description":"Updated source (URL, book, etc.)."},"source_url":{"type":["string","null"],"description":"Updated direct URL to the original recipe."},"notes":{"type":["string","null"],"description":"Updated recipe notes."},"image_url":{"type":["string","null"],"description":"Updated relative storage path in the recipe-images private bucket."},"ingredients":{"type":"array","description":"If provided, fully replaces all ingredients. Each item must include name, quantity, and unit.","items":{"type":"object","required":["name","quantity","unit"],"properties":{"name":{"type":"string","description":"Ingredient name."},"quantity":{"type":"number","description":"Amount needed."},"unit":{"type":"string","description":"Unit of measurement (g, ml, cup, tbsp, etc.)."},"notes":{"type":"string","description":"Preparation notes (e.g. 'diced', 'room temperature')."},"category":{"type":"string","description":"Ingredient category (e.g. 'Produce', 'Dairy', 'Meat')."}}}},"steps":{"type":"array","description":"If provided, fully replaces all steps. Steps are numbered in order.","items":{"type":"object","required":["instruction"],"properties":{"instruction":{"type":"string","description":"Step instruction text."}}}},"tags":{"type":"array","items":{"type":"string"},"description":"If provided, fully replaces all tags. Tag names are lowercased and trimmed automatically."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":["string","null"]},"servings":{"type":"number"},"prep_time_minutes":{"type":["number","null"]},"cook_time_minutes":{"type":["number","null"]},"total_time_minutes":{"type":["number","null"]},"source":{"type":["string","null"]},"is_favorite":{"type":"boolean"},"image_url":{"type":["string","null"],"description":"Relative storage path in the recipe-images private bucket, or null if no image. Use image_signed_url for display."},"image_signed_url":{"type":["string","null"],"description":"Time-limited signed URL for displaying the image (valid for 1 hour), or null if no image."},"tags":{"type":"array","items":{"type":"string"}},"steps":{"type":"array","items":{"type":"object","properties":{"step_number":{"type":"number"},"instruction":{"type":"string"}}}},"ingredients":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"notes":{"type":["string","null"]},"category":{"type":["string","null"]}}}},"nutrition":{"type":["object","null"],"description":"Calculated nutrition per serving (when ingredient data is available).","properties":{"calories":{"type":"number"},"protein_g":{"type":"number"},"carbs_g":{"type":"number"},"fat_g":{"type":"number"},"fiber_g":{"type":"number"}}},"cost":{"type":["object","null"],"description":"Cost estimate in ISK based on ingredient pricing data.","properties":{"total_cost":{"type":"number"},"cost_per_serving":{"type":"number"},"priced_ingredient_count":{"type":"number"},"unpriced_ingredient_count":{"type":["number","null"]}}},"url":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/plan/week","method":"GET","description":"Get the meal plan for a given week, showing all planned meals grouped by date and meal type. Includes recipe titles and attendee names so you can describe the full weekly plan without follow-up calls. Use to answer 'What's for dinner this week?' or review the current plan.","inputSchema":{"type":"object","properties":{"week_start":{"type":"string","format":"date","description":"Monday of the week to fetch (YYYY-MM-DD). Defaults to current week."}}},"outputSchema":{"type":"object","properties":{"week_start":{"type":"string","format":"date"},"week_end":{"type":"string","format":"date"},"meals":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"meal_date":{"type":"string","format":"date"},"meal_type":{"type":"string","enum":["breakfast","lunch","dinner","snack"]},"meal_source":{"type":"string","enum":["home_cooked","dining_out","takeout","invited"]},"servings":{"type":"number"},"recipes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}}}},"attendees":{"type":"array","items":{"type":"object","properties":{"family_member_id":{"type":"string"},"display_name":{"type":"string"},"description":{"type":["string","null"]},"meal_character":{"type":"array","items":{"type":"string"}}}}},"location":{"type":["string","null"]},"estimated_cost":{"type":["number","null"]},"notes":{"type":["string","null"]}}}},"summary":{"type":"object","properties":{"total_meals":{"type":"number"},"home_cooked":{"type":"number"},"dining_out":{"type":"number"},"empty_slots":{"type":"number"}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/plan/add","method":"POST","description":"Add a meal to the weekly plan. Supports home-cooked meals with recipe references, as well as dining out, takeout, and invited meals with location and cost tracking. For home-cooked meals, automatically creates person_meals entries for each attendee (linked to the meal plan). Each date+meal_type slot can only have one plan — use this to fill empty slots.","inputSchema":{"type":"object","required":["meal_date","meal_type","meal_source"],"properties":{"meal_date":{"type":"string","format":"date","description":"Date for the meal (YYYY-MM-DD)."},"meal_type":{"type":"string","enum":["breakfast","lunch","dinner","snack"],"description":"Which meal of the day."},"meal_source":{"type":"string","enum":["home_cooked","dining_out","takeout","invited","school","work","packed_lunch","other","leftover"],"description":"How the meal will be sourced. Use 'leftover' to plan a meal from existing leftovers — requires leftover_id or recipe_id to identify which leftover to consume."},"recipe_ids":{"type":"array","items":{"type":"string"},"description":"Recipe IDs for home-cooked meals. Can include multiple recipes per meal."},"leftover_id":{"type":"string","description":"ID of the leftover to consume (for meal_source='leftover'). If omitted, auto-picks the most urgent leftover matching recipe_id."},"family_member_ids":{"type":"array","items":{"type":"string"},"description":"Family member IDs (from public.family_members) for meal attendees. If omitted for home-cooked meals, uses members marked as default in family preferences."},"location":{"type":"string","description":"Restaurant or location name (for dining out/takeout)."},"estimated_cost":{"type":"number","description":"Estimated cost in ISK (for dining out/takeout)."},"people_count":{"type":"number","description":"Number of people (for non-home-cooked meals)."},"servings":{"type":"number","description":"Number of servings to consume (for leftover meals, defaults to 1)."},"notes":{"type":"string","description":"Additional notes about the meal."}}},"outputSchema":{"type":"object","properties":{"meal_plan_id":{"type":"string"},"meal_date":{"type":"string"},"meal_type":{"type":"string"},"meal_source":{"type":"string"},"servings":{"type":"number"},"recipes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"}}}},"attendees":{"type":"array","items":{"type":"object","properties":{"family_member_id":{"type":"string"},"display_name":{"type":"string"},"servings":{"type":"number"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/plan/:meal_plan_id","method":"DELETE","description":"Remove a meal from the plan, freeing up the time slot. Use when a user wants to change plans or clear a meal slot.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"},"meal_plan_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/shopping-list/generate","method":"POST","description":"Generate a shopping list from the meal plan for a date range. Aggregates all ingredients from home-cooked meals, scales quantities by serving ratios, and deduplicates by ingredient+unit. Any existing active shopping list is automatically completed first. Use when a user asks 'What do I need to buy this week?' or wants to prepare for grocery shopping.","inputSchema":{"type":"object","required":["start_date","end_date"],"properties":{"start_date":{"type":"string","format":"date","description":"Start of the date range (YYYY-MM-DD)."},"end_date":{"type":"string","format":"date","description":"End of the date range (YYYY-MM-DD)."},"name":{"type":"string","description":"Optional name for the shopping list. Defaults to 'Shopping list for [start] - [end]'."}}},"outputSchema":{"type":"object","properties":{"shopping_list_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"item_count":{"type":"number"},"items_by_category":{"type":"object","description":"Items grouped by category (e.g. Produce, Dairy, Meat). Each category contains an array of { name, quantity, unit }.","additionalProperties":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"}}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/shopping-list","method":"GET","description":"Get the currently active shopping list with all items grouped by category. Includes ingredient names, quantities, units, and purchased status. Returns null if no active list exists. Use to read back the shopping list to the user or check what still needs to be bought.","outputSchema":{"type":"object","properties":{"shopping_list":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"start_date":{"type":["string","null"],"format":"date"},"end_date":{"type":["string","null"],"format":"date"},"created_at":{"type":"string"}}},"summary":{"type":"object","properties":{"total_items":{"type":"number"},"purchased":{"type":"number"},"remaining":{"type":"number"}}},"items_by_category":{"type":"object","description":"Items grouped by category. Each category contains an array of items with id, name, quantity, unit, is_purchased, and notes.","additionalProperties":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"is_purchased":{"type":"boolean"},"notes":{"type":["string","null"]}}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/person-meals","method":"POST","description":"Log what a specific family member ate for a meal slot. Creates or updates a person_meal entry (upserts on family_member + date + meal_type). Use to track what kids ate at school, what someone had for lunch at work, or to record any individual meal. The description is free-text (e.g. 'kjúklingasúpa') and meal_character tags describe the meal's nature (e.g. 'light', 'soup', 'protein-heavy').","inputSchema":{"type":"object","required":["family_member_id","meal_date","meal_type","meal_source"],"properties":{"family_member_id":{"type":"string","description":"ID of the family member (from public.family_members)."},"meal_date":{"type":"string","format":"date","description":"Date of the meal (YYYY-MM-DD)."},"meal_type":{"type":"string","enum":["breakfast","lunch","dinner","snack"],"description":"Which meal of the day."},"meal_source":{"type":"string","enum":["home_cooked","school","work","dining_out","takeout","invited","packed_lunch","other"],"description":"Where/how the meal was sourced. Use 'school' for kids' school lunches, 'work' for workplace canteen, etc."},"description":{"type":"string","description":"Free-text description of what was eaten (e.g. 'kjúklingasúpa', 'pasta with meatballs')."},"meal_character":{"type":"array","items":{"type":"string"},"description":"Tags describing the meal's character: protein-heavy, light, soup, carb-heavy, salad, fish, comfort-food, vegetarian."},"meal_plan_id":{"type":"string","description":"Link to a home-cooked meal plan (if this person_meal comes from a planned meal)."},"location":{"type":"string","description":"Where the meal was eaten (e.g. school name, restaurant)."},"notes":{"type":"string","description":"Additional notes."},"rating":{"type":"number","description":"Star rating 1-5. Nullable — not all family members can express preference."},"consumption":{"type":"string","enum":["ate_well","ate_some","didnt_eat"],"description":"How much was eaten. Designed for young children but usable by anyone."},"feedback_notes":{"type":"string","description":"Short comment about the meal (e.g. 'too salty', 'loved the sauce')."},"would_eat_again":{"type":"boolean","description":"Quick yes/no for meal planning — most actionable feedback field."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"family_member_id":{"type":"string"},"display_name":{"type":"string"},"meal_date":{"type":"string","format":"date"},"meal_type":{"type":"string"},"meal_source":{"type":"string"},"description":{"type":["string","null"]},"meal_character":{"type":"array","items":{"type":"string"}},"meal_plan_id":{"type":["string","null"]},"location":{"type":["string","null"]},"notes":{"type":["string","null"]},"rating":{"type":["number","null"]},"consumption":{"type":["string","null"]},"feedback_notes":{"type":["string","null"]},"would_eat_again":{"type":["boolean","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/person-meals/day","method":"GET","description":"Get all person-level meals for a given date, grouped by meal type (breakfast, lunch, dinner, snack). Shows what each family member ate, with meal source, description, character tags, and linked meal plan info. This is THE key planning endpoint — use it to see what everyone had for lunch before planning dinner.","inputSchema":{"type":"object","required":["date"],"properties":{"date":{"type":"string","format":"date","description":"Date to fetch (YYYY-MM-DD)."}}},"outputSchema":{"type":"object","properties":{"date":{"type":"string","format":"date"},"breakfast":{"type":"array","description":"Person meals for breakfast."},"lunch":{"type":"array","description":"Person meals for lunch."},"dinner":{"type":"array","description":"Person meals for dinner."},"snack":{"type":"array","description":"Person meals for snacks."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/person-meals/batch","method":"POST","description":"Log meals for multiple family members at once. Ideal for recording school lunches for several kids in a single call — provide the same description and source for multiple family_member_ids. Each entry is upserted independently. Maximum 20 entries per batch.","inputSchema":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","items":{"type":"object","required":["family_member_id","meal_date","meal_type","meal_source"],"properties":{"family_member_id":{"type":"string","description":"Family member ID (from public.family_members)."},"meal_date":{"type":"string","format":"date","description":"Date (YYYY-MM-DD)."},"meal_type":{"type":"string","enum":["breakfast","lunch","dinner","snack"]},"meal_source":{"type":"string","enum":["home_cooked","school","work","dining_out","takeout","invited","packed_lunch","other"]},"description":{"type":"string"},"meal_character":{"type":"array","items":{"type":"string"}},"rating":{"type":"number","description":"Star rating 1-5."},"consumption":{"type":"string","enum":["ate_well","ate_some","didnt_eat"],"description":"How much was eaten."},"feedback_notes":{"type":"string","description":"Short comment about the meal."},"would_eat_again":{"type":"boolean","description":"Quick yes/no for meal planning."}}},"description":"Array of person_meal entries to create/update. Maximum 20 per batch."}}},"outputSchema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"family_member_id":{"type":"string"},"display_name":{"type":"string"},"meal_date":{"type":"string"},"meal_type":{"type":"string"},"meal_source":{"type":"string"},"description":{"type":["string","null"]},"meal_character":{"type":"array","items":{"type":"string"}},"rating":{"type":["number","null"]},"consumption":{"type":["string","null"]},"feedback_notes":{"type":["string","null"]},"would_eat_again":{"type":["boolean","null"]}}}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/person-meals/:id/feedback","method":"PATCH","description":"Add or update feedback on an existing person meal. Use after eating to rate the meal — accepts any subset of feedback fields. This is the 'rate after eating' endpoint.","inputSchema":{"type":"object","properties":{"rating":{"type":"number","description":"Star rating 1-5. Nullable — not all family members can express preference."},"consumption":{"type":"string","enum":["ate_well","ate_some","didnt_eat"],"description":"How much was eaten. Designed for young children but usable by anyone."},"feedback_notes":{"type":"string","description":"Short comment about the meal (e.g. 'too salty', 'loved the sauce')."},"would_eat_again":{"type":"boolean","description":"Quick yes/no for meal planning — most actionable feedback field."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"family_member_id":{"type":"string"},"display_name":{"type":"string"},"meal_date":{"type":"string","format":"date"},"meal_type":{"type":"string"},"meal_source":{"type":"string"},"description":{"type":["string","null"]},"meal_character":{"type":"array","items":{"type":"string"}},"meal_plan_id":{"type":["string","null"]},"location":{"type":["string","null"]},"notes":{"type":["string","null"]},"rating":{"type":["number","null"]},"consumption":{"type":["string","null"]},"feedback_notes":{"type":["string","null"]},"would_eat_again":{"type":["boolean","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/person-meals/feedback/batch","method":"POST","description":"Rate meals for multiple family members in one call. Ideal for after dinner — record everyone's reaction in a single request. Each entry targets an existing person_meal by ID and accepts any subset of feedback fields. Maximum 20 entries per batch.","inputSchema":{"type":"object","required":["entries"],"properties":{"entries":{"type":"array","items":{"type":"object","required":["person_meal_id"],"properties":{"person_meal_id":{"type":"string","description":"ID of the person_meal to add feedback to."},"rating":{"type":"number","description":"Star rating 1-5."},"consumption":{"type":"string","enum":["ate_well","ate_some","didnt_eat"],"description":"How much was eaten."},"feedback_notes":{"type":"string","description":"Short comment about the meal."},"would_eat_again":{"type":"boolean","description":"Quick yes/no for meal planning."}}},"description":"Array of feedback entries. Each must reference an existing person_meal_id. Maximum 20 per batch."}}},"outputSchema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"family_member_id":{"type":"string"},"display_name":{"type":"string"},"meal_date":{"type":"string"},"meal_type":{"type":"string"},"meal_source":{"type":"string"},"rating":{"type":["number","null"]},"consumption":{"type":["string","null"]},"feedback_notes":{"type":["string","null"]},"would_eat_again":{"type":["boolean","null"]}}}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/person-meals/:id","method":"DELETE","description":"Delete a single person meal entry. Use when a meal was logged incorrectly or needs to be removed.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/ingredients","method":"GET","description":"List all ingredients in the user's family library. Optionally include pricing data (reference price, unit, per-gram cost). Filter by category, search by name, or find ingredients that still need pricing. Use when reviewing ingredient prices or checking which items need price updates.","inputSchema":{"type":"object","properties":{"include_pricing":{"type":"boolean","description":"If true, include pricing columns (reference_price, reference_price_unit, price_per_gram, has_price)."},"has_price":{"type":"boolean","description":"Filter by pricing status — true for priced, false for unpriced. Only works when include_pricing=true."},"category":{"type":"string","description":"Filter by ingredient category (exact match)."},"search":{"type":"string","description":"Search ingredient name (case-insensitive partial match)."}}},"outputSchema":{"type":"object","properties":{"ingredients":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"category":{"type":["string","null"]},"default_unit":{"type":["string","null"]},"reference_price":{"type":["number","null"]},"reference_price_unit":{"type":["string","null"]},"reference_price_quantity":{"type":["number","null"]},"has_price":{"type":"boolean"},"price_per_gram":{"type":["number","null"]}}}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/ingredients/:ingredient_id/price","method":"PATCH","description":"Update the reference price for a single ingredient. Prices are in ISK. The unit is automatically normalized to lowercase. Use when a user provides a grocery price for an ingredient.","inputSchema":{"type":"object","required":["reference_price","reference_price_unit"],"properties":{"reference_price":{"type":"number","description":"Price in ISK (non-negative integer)."},"reference_price_unit":{"type":"string","description":"Unit the price refers to (e.g. 'kg', 'g', 'ml', 'stk', 'pakki')."},"reference_price_quantity":{"type":"number","description":"How many of the unit the price covers (default 1). E.g. 500 for '500g'."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"reference_price":{"type":"number"},"reference_price_unit":{"type":"string"},"reference_price_quantity":{"type":"number"},"price_updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/ingredients/prices/bulk","method":"POST","description":"Bulk-update pricing for multiple ingredients at once. Maximum 50 items per batch. Use when importing prices from a grocery receipt or updating multiple items simultaneously.","inputSchema":{"type":"object","required":["prices"],"properties":{"prices":{"type":"array","items":{"type":"object","required":["ingredient_id","reference_price","reference_price_unit"],"properties":{"ingredient_id":{"type":"string","description":"ID of the ingredient to update."},"reference_price":{"type":"number","description":"Price in ISK (non-negative integer)."},"reference_price_unit":{"type":"string","description":"Unit the price refers to (e.g. 'kg', 'g', 'ml', 'stk')."},"reference_price_quantity":{"type":"number","description":"How many of the unit the price covers (default 1)."}}},"description":"Array of ingredient price updates. Maximum 50 per batch."}}},"outputSchema":{"type":"object","properties":{"updated_count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/ingredients/create","method":"POST","description":"Create a new ingredient in the family library. Performs case-insensitive deduplication — if an ingredient with the same name already exists, returns it instead of creating a duplicate. Use when mapping receipt line items to ingredients or when a user wants to add a new ingredient.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Ingredient name."},"category":{"type":"string","description":"Category (e.g. 'dairy', 'produce')."},"default_unit":{"type":"string","description":"Default unit of measure (e.g. 'l', 'kg', 'stk')."},"calories_per_100g":{"type":"number","description":"Calories per 100g."},"protein_g_per_100g":{"type":"number","description":"Protein grams per 100g."},"carbs_g_per_100g":{"type":"number","description":"Carbs grams per 100g."},"fat_g_per_100g":{"type":"number","description":"Fat grams per 100g."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/ingredients/:ingredient_id/price-observations","method":"GET","description":"List price observations for an ingredient, sorted by observed_at descending. Shows price history from receipts, manual entries, and imports. Use when viewing an ingredient's price trend or checking historical prices.","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","default":50,"description":"Max observations to return."},"offset":{"type":"integer","default":0,"description":"Pagination offset."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/ingredients/:ingredient_id/price-observations","method":"POST","description":"Record a price observation for an ingredient. The database trigger automatically updates the ingredient's reference_price if this is the most recent observation. Use when manually logging a price or when linking a receipt line item to an ingredient.","inputSchema":{"type":"object","required":["price","quantity","unit","observed_at","source"],"properties":{"price":{"type":"number","description":"Total price paid (e.g. 299 ISK)."},"quantity":{"type":"number","description":"Number of units (e.g. 1 for 1L)."},"unit":{"type":"string","description":"Unit of measure (l, kg, g, ml, stk, etc.)."},"observed_at":{"type":"string","description":"Date of purchase (YYYY-MM-DD)."},"source":{"type":"string","enum":["receipt","manual","import"],"description":"Where this price came from."},"source_line_item_id":{"type":"string","description":"Optional budgeting line item ID."},"merchant_id":{"type":"string","description":"Optional merchant ID."},"currency":{"type":"string","default":"ISK","description":"Currency code."},"notes":{"type":"string","description":"Optional notes."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id/cost","method":"GET","description":"Get the full cost breakdown for a recipe. Computes total cost and per-serving cost in ISK using ingredient reference prices and unit conversions. By default uses the active version's ingredients. Pass version_id to compute cost for a specific version.","inputSchema":{"type":"object","properties":{"version_id":{"type":"string","description":"Optional version UUID to compute cost for a specific version. Defaults to the active version."}}},"outputSchema":{"type":"object","properties":{"recipe_id":{"type":"string"},"title":{"type":"string"},"servings":{"type":"number"},"total_cost":{"type":"number","description":"Total estimated cost in ISK."},"cost_per_serving":{"type":"number","description":"Cost per serving in ISK."},"priced_ingredient_count":{"type":"number"},"unpriced_ingredient_count":{"type":["number","null"]},"unpriced_ingredients":{"type":"array","items":{"type":"string"},"description":"Names of ingredients without pricing data."},"cost_breakdown":{"type":"array","items":{"type":"object","properties":{"ingredient_name":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string"},"ingredient_cost_isk":{"type":"number"}}},"description":"Per-ingredient cost breakdown."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/leftovers","method":"POST","description":"Log leftovers after a meal. Creates a leftover record with servings available and a use-by date. Use when a user says they have leftover food (e.g. 'We have 2 servings of pasta left over from dinner').","inputSchema":{"type":"object","required":["recipe_id","servings_remaining"],"properties":{"recipe_id":{"type":"string","description":"Recipe that was cooked."},"servings_remaining":{"type":"number","description":"How many servings are left over."},"source_meal_plan_id":{"type":"string","description":"Meal plan entry that produced these leftovers (optional)."},"use_by_date":{"type":"string","format":"date","description":"Use-by date (YYYY-MM-DD). Defaults to today + 3 days."},"notes":{"type":"string","description":"Storage notes (e.g. 'in blue container', 'in freezer')."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"recipe_id":{"type":"string"},"recipe_title":{"type":"string"},"servings_remaining":{"type":"number"},"servings_original":{"type":"number"},"created_date":{"type":"string","format":"date"},"use_by_date":{"type":"string","format":"date"},"status":{"type":"string"},"notes":{"type":["string","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/leftovers","method":"GET","description":"List leftovers in the fridge. By default returns only active (available, not expired) leftovers sorted by urgency. Use to answer 'What leftovers do we have?' or check what needs to be eaten soon.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["available","consumed","expired","discarded"],"description":"Filter by status. Default: available only."},"recipe_id":{"type":"string","description":"Filter by specific recipe."},"include_expired":{"type":"boolean","description":"Include expired/discarded leftovers for history."},"is_urgent":{"type":"boolean","description":"Only return leftovers expiring today or tomorrow. Useful for morning briefing integration."}}},"outputSchema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"recipe_id":{"type":"string"},"recipe_title":{"type":"string"},"servings_remaining":{"type":"number"},"servings_original":{"type":"number"},"created_date":{"type":"string","format":"date"},"use_by_date":{"type":"string","format":"date"},"days_until_expiry":{"type":"number"},"is_urgent":{"type":"boolean"},"notes":{"type":["string","null"]}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/leftovers/:id","method":"PATCH","description":"Update a leftover record. Use to mark leftovers as discarded ('we threw it away'), update notes, adjust servings, or change the use-by date.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["available","consumed","expired","discarded"],"description":"New status. Use 'discarded' when food was thrown away."},"notes":{"type":"string","description":"Updated storage notes."},"use_by_date":{"type":"string","format":"date","description":"Updated use-by date (YYYY-MM-DD)."},"servings_remaining":{"type":"number","description":"Manual adjustment of remaining servings."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"recipe_id":{"type":"string"},"servings_remaining":{"type":"number"},"servings_original":{"type":"number"},"created_date":{"type":"string","format":"date"},"use_by_date":{"type":"string","format":"date"},"status":{"type":"string"},"notes":{"type":["string","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/leftovers/:id/consume","method":"POST","description":"Consume servings from a leftover. Use when someone eats leftover food (e.g. 'I had one serving of the leftover pasta for lunch'). Decrements remaining servings and auto-marks as consumed when depleted.","inputSchema":{"type":"object","required":["servings_consumed"],"properties":{"servings_consumed":{"type":"number","description":"Number of servings consumed."},"meal_plan_id":{"type":"string","description":"Link to a meal plan entry (if consuming as part of a planned meal)."}}},"outputSchema":{"type":"object","properties":{"consumption_id":{"type":"string"},"leftover_id":{"type":"string"},"servings_consumed":{"type":"number"},"servings_remaining":{"type":"number"},"leftover_status":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/leftovers/suggestions","method":"GET","description":"Get leftover-based meal suggestions for planning. Returns available leftovers grouped by recipe, sorted by urgency (expiring soonest first). Use when planning meals to surface 'You have leftovers!' suggestions, especially for lunch.","outputSchema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"recipe_id":{"type":"string"},"recipe_title":{"type":"string"},"total_servings":{"type":"number"},"most_urgent_use_by":{"type":"string","format":"date"},"is_urgent":{"type":"boolean"},"leftover_ids":{"type":"array","items":{"type":"string"}}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/leftovers/expire","method":"POST","description":"Trigger manual expiry check. Finds all available leftovers past their use-by date and marks them as expired. Returns the list of newly expired items. Called by the morning briefing or scheduled tasks.","outputSchema":{"type":"object","properties":{"expired_count":{"type":"number"},"expired_items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"recipe_id":{"type":"string"},"recipe_title":{"type":"string"},"servings_wasted":{"type":"number"},"use_by_date":{"type":"string","format":"date"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/leftovers/waste","method":"GET","description":"Get food waste data with aggregate summary. Shows individual waste events (expired and discarded leftovers) with optional cost estimates. Use to answer 'How much food are we wasting?' or track waste trends.","inputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["week","month","year","all"],"description":"Time period for waste data. Default: month."}}},"outputSchema":{"type":"object","properties":{"period":{"type":"string"},"summary":{"type":"object","properties":{"total_waste_events":{"type":"number"},"total_servings_wasted":{"type":"number"},"total_cost_wasted":{"type":["number","null"]}}},"events":{"type":"array","items":{"type":"object","properties":{"leftover_id":{"type":"string"},"recipe_id":{"type":"string"},"recipe_title":{"type":"string"},"servings_wasted":{"type":"number"},"status":{"type":"string"},"created_date":{"type":"string","format":"date"},"use_by_date":{"type":"string","format":"date"},"estimated_cost_wasted":{"type":["number","null"]}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id/image","method":"POST","description":"Upload or replace a recipe hero image. Accepts EITHER a publicly accessible image_url (preferred — avoids base64 size limits) OR base64-encoded image data. The image is stored in a private Supabase Storage bucket (recipe-images). The response includes storage_path (relative path within the bucket) and image_signed_url (a time-limited URL valid for 1 hour). Use image_signed_url for display — do not cache it beyond expiry.","inputSchema":{"type":"object","required":[],"properties":{"image_url":{"type":"string","description":"Publicly accessible URL of the image to upload. The server fetches it directly — avoids base64 size limits. Preferred over the image parameter. Supports JPEG, PNG, and WebP (max 10 MB)."},"image":{"type":"string","description":"Base64-encoded image data. May include a data URI prefix (e.g. 'data:image/jpeg;base64,...') — the prefix will be stripped automatically. Max 5 MB decoded. Use image_url instead when possible."},"content_type":{"type":"string","description":"MIME type of the image. Only needed with base64 image parameter. Supported values: 'image/jpeg', 'image/png', 'image/webp'. Defaults to 'image/jpeg'. Ignored when using image_url (detected automatically)."}}},"outputSchema":{"type":"object","properties":{"storage_path":{"type":"string","description":"Relative path within the recipe-images private bucket (e.g. {user_id}/{recipe_id}.jpg)."},"image_signed_url":{"type":["string","null"],"description":"Time-limited signed URL for displaying the image (valid for 1 hour). Do not cache beyond expiry."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/meals/recipes/:recipe_id/image","method":"DELETE","description":"Remove a recipe's hero image from the private recipe-images bucket and clear the image_url.","inputSchema":{"type":"object","properties":{}},"outputSchema":{"type":"object","properties":{"message":{"type":"string","description":"Confirmation message."}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"meals"},{"path":"/api/v1/fitness/programs/create","method":"POST","description":"Create a new workout program (template) with a list of exercises and their targets. Use when a user describes a training program they want to follow — for example 'create a push/pull/legs split' or 'set up a 5x5 strength program'. Each exercise references the exercise catalog by ID and can include target sets, rep ranges, RPE, rest times, and superset grouping.","inputSchema":{"type":"object","required":["name","exercises"],"properties":{"name":{"type":"string","description":"Program name (e.g. 'Push/Pull/Legs — Push Day')."},"description":{"type":"string","description":"Optional description of the program's goals or structure."},"target_muscles":{"type":"array","items":{"type":"string","enum":["chest","back","shoulders","biceps","triceps","forearms","quadriceps","hamstrings","glutes","calves","core","full_body"]},"description":"Primary muscle groups targeted by this program."},"estimated_duration_minutes":{"type":"number","description":"Estimated workout duration in minutes."},"exercises":{"type":"array","minItems":1,"items":{"type":"object","required":["exercise_id"],"properties":{"exercise_id":{"type":"string","description":"UUID of the exercise from the exercise catalog."},"sort_order":{"type":"number","description":"Position in the workout (0-indexed). Defaults to array index."},"target_sets":{"type":"number","description":"Number of target sets."},"target_reps_min":{"type":"number","description":"Minimum target reps per set."},"target_reps_max":{"type":"number","description":"Maximum target reps per set."},"target_rpe":{"type":"number","description":"Target Rate of Perceived Exertion (1-10)."},"target_duration_seconds":{"type":"number","description":"Target duration for timed exercises."},"rest_seconds":{"type":"number","description":"Rest time between sets in seconds."},"notes":{"type":"string","description":"Exercise-specific notes or cues."},"superset_group":{"type":"number","description":"Group number for supersets. Exercises with the same group are performed back-to-back."}}},"description":"Ordered list of exercises with their targets."}}},"outputSchema":{"type":"object","properties":{"program_id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"exercise_count":{"type":"number"},"exercises":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Template exercise ID."},"exercise_id":{"type":"string"},"exercise_name":{"type":"string"},"sort_order":{"type":"number"},"target_sets":{"type":["number","null"]},"target_reps_min":{"type":["number","null"]},"target_reps_max":{"type":["number","null"]}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/programs","method":"GET","description":"List all workout programs (templates) for the user. Use when a user asks 'What programs do I have?' or wants to browse their saved routines. Supports text search on program name, filtering by favorites and archived status, and pagination. Returns programs sorted by favorites first, then most recently used. Each program includes an exercise_count for quick overview.","inputSchema":{"type":"object","properties":{"search":{"type":"string","description":"Text search on program name (case-insensitive partial match)."},"is_favorite":{"type":"boolean","description":"Filter to favorite programs only."},"is_archived":{"type":"boolean","description":"Filter by archived status. Default: false (only active programs)."},"limit":{"type":"number","description":"Max results (1-100, default 50)."},"offset":{"type":"number","description":"Pagination offset (default 0)."}}},"outputSchema":{"type":"object","properties":{"programs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"target_muscles":{"type":"array","items":{"type":"string"}},"estimated_duration_minutes":{"type":["number","null"]},"is_favorite":{"type":"boolean"},"is_archived":{"type":"boolean"},"last_used_at":{"type":["string","null"]},"use_count":{"type":"number"},"exercise_count":{"type":"number"}}}},"count":{"type":"number","description":"Total number of matching programs."},"has_more":{"type":"boolean","description":"Whether there are more programs beyond this page."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/programs/:id","method":"GET","description":"Get full details for a single workout program including all exercises with their targets and exercise catalog metadata (names, muscle groups). Use when a user asks about a specific program — e.g. 'Show me my Push Day program' or when you need the exercise list to suggest modifications.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"target_muscles":{"type":"array","items":{"type":"string"}},"estimated_duration_minutes":{"type":["number","null"]},"is_favorite":{"type":"boolean"},"is_archived":{"type":"boolean"},"last_used_at":{"type":["string","null"]},"use_count":{"type":"number"},"exercises":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Template exercise ID."},"exercise_id":{"type":"string"},"exercise_name":{"type":"string"},"primary_muscle":{"type":"string"},"sort_order":{"type":"number"},"target_sets":{"type":["number","null"]},"target_reps_min":{"type":["number","null"]},"target_reps_max":{"type":["number","null"]},"target_rpe":{"type":["number","null"]},"target_duration_seconds":{"type":["number","null"]},"rest_seconds":{"type":["number","null"]},"notes":{"type":["string","null"]},"superset_group":{"type":["number","null"]}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/programs/:id","method":"PATCH","description":"Update a program's metadata — name, description, target muscles, estimated duration, favorite status, or archived status. Use when a user wants to rename a program, mark it as a favorite, or update its description. Does not modify the exercise list (use POST programs/:id/exercises for that).","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Program name."},"description":{"type":["string","null"],"description":"Program description."},"target_muscles":{"type":"array","items":{"type":"string","enum":["chest","back","shoulders","biceps","triceps","forearms","quadriceps","hamstrings","glutes","calves","core","full_body"]},"description":"Primary muscle groups targeted."},"estimated_duration_minutes":{"type":["number","null"],"description":"Estimated duration in minutes."},"is_favorite":{"type":"boolean","description":"Mark as favorite."},"is_archived":{"type":"boolean","description":"Archive or unarchive."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"target_muscles":{"type":"array","items":{"type":"string"}},"estimated_duration_minutes":{"type":["number","null"]},"is_favorite":{"type":"boolean"},"is_archived":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/programs/:id/exercises","method":"POST","description":"Replace all exercises in a program with a new list. Use when a user wants to restructure a program — e.g. 'swap the bench press for incline press' or 'reorder the exercises in my leg day'. This is a full replacement: all existing exercises are removed and the new list is inserted. Send the complete desired exercise list.","inputSchema":{"type":"object","required":["exercises"],"properties":{"exercises":{"type":"array","minItems":1,"items":{"type":"object","required":["exercise_id"],"properties":{"exercise_id":{"type":"string","description":"UUID of the exercise from the exercise catalog."},"sort_order":{"type":"number","description":"Position in the workout (0-indexed). Defaults to array index."},"target_sets":{"type":"number","description":"Number of target sets."},"target_reps_min":{"type":"number","description":"Minimum target reps per set."},"target_reps_max":{"type":"number","description":"Maximum target reps per set."},"target_rpe":{"type":"number","description":"Target Rate of Perceived Exertion (1-10)."},"target_duration_seconds":{"type":"number","description":"Target duration for timed exercises."},"rest_seconds":{"type":"number","description":"Rest time between sets in seconds."},"notes":{"type":"string","description":"Exercise-specific notes or cues."},"superset_group":{"type":"number","description":"Group number for supersets."}}},"description":"Complete ordered list of exercises to replace the current list."}}},"outputSchema":{"type":"object","properties":{"program_id":{"type":"string"},"exercise_count":{"type":"number"},"exercises":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"exercise_id":{"type":"string"},"exercise_name":{"type":"string"},"primary_muscle":{"type":"string"},"sort_order":{"type":"number"},"target_sets":{"type":["number","null"]},"target_reps_min":{"type":["number","null"]},"target_reps_max":{"type":["number","null"]}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/programs/:id","method":"DELETE","description":"Archive a program (soft delete). Use when a user says 'Delete my old program' or 'I don't use this routine anymore'. Sets is_archived to true rather than hard-deleting, preserving workout history. Fails if the program has active or scheduled workouts — those must be completed or cancelled first.","outputSchema":{"type":"object","properties":{"archived":{"type":"boolean"},"program_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/workouts/upcoming","method":"GET","description":"Get upcoming scheduled workouts with their planned exercises and last-performance context. Use when a user asks 'What's my next workout?' or wants to prepare for an upcoming session. Returns both explicitly scheduled workouts and recurring schedule occurrences. Each exercise includes the target sets/reps from the template and what the user actually did last time — useful for suggesting weights or noticing progression.","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Maximum number of upcoming workouts to return (default: 7, max: 30)."},"program_id":{"type":"string","description":"Filter to workouts from a specific program/template UUID."}}},"outputSchema":{"type":"object","properties":{"workouts":{"type":"array","items":{"type":"object","properties":{"id":{"type":["string","null"],"description":"Workout ID (null for recurring schedule projections)."},"name":{"type":"string"},"scheduled_for":{"type":["string","null"],"format":"date"},"source":{"type":"string","enum":["scheduled","recurring"]},"template_id":{"type":["string","null"]},"template_name":{"type":["string","null"]},"exercises":{"type":"array","items":{"type":"object","properties":{"exercise_id":{"type":"string"},"exercise_name":{"type":"string"},"primary_muscle":{"type":"string"},"exercise_type":{"type":"string"},"target_sets":{"type":["number","null"]},"target_reps_min":{"type":["number","null"]},"target_reps_max":{"type":["number","null"]},"target_rpe":{"type":["number","null"]},"rest_seconds":{"type":["number","null"]},"last_performance":{"type":["object","null"],"description":"What the user did last time for this exercise. For cable sets, plate counts and resolved actual_kg are returned alongside the raw weight_kg field.","properties":{"date":{"type":"string","format":"date"},"sets":{"type":"array","items":{"type":"object","properties":{"set_number":{"type":"number"},"weight_kg":{"type":["number","null"],"description":"Loaded weight for free-weight sets. NULL for cable sets — see plates_count / actual_kg."},"reps":{"type":["number","null"]},"rpe":{"type":["number","null"]},"plates_count":{"type":["number","null"],"description":"Bilateral cable plate count."},"plates_left":{"type":["number","null"],"description":"Unilateral cable: left side plate count."},"plates_right":{"type":["number","null"],"description":"Unilateral cable: right side plate count."},"equipment_item_id":{"type":["string","null"],"description":"Cable machine UUID."},"pulley_name":{"type":["string","null"],"description":"Pulley position name on the machine."},"actual_kg":{"type":["number","null"],"description":"Server-resolved kg for bilateral cable sets."},"actual_kg_left":{"type":["number","null"],"description":"Server-resolved kg for the left side of unilateral cable sets."},"actual_kg_right":{"type":["number","null"],"description":"Server-resolved kg for the right side of unilateral cable sets."}}}}}}}}}}}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/history/recent","method":"GET","description":"Get recent workout history with full exercise and set details. Use when a user asks about their recent training — for example 'How did my last leg day go?' or 'Show me my workouts this week'. Returns completed workouts in reverse chronological order with every exercise and set logged. Each set includes weight_kg for free-weight sets OR plate count + actual_kg for cable-machine sets (the server resolves cable plates to kg via the equipment's weight_config; clients should display actual_kg for analytics and trend graphs while showing plates_count alongside in cable-set UIs). Unilateral cable sets expose plates_left/plates_right + actual_kg_left/actual_kg_right separately. Supports filtering by exercise and date range for targeted analysis.","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Number of workouts to return (default: 10, max: 50)."},"offset":{"type":"number","description":"Pagination offset (default: 0)."},"exercise_id":{"type":"string","description":"Filter to workouts containing a specific exercise UUID."},"since":{"type":"string","format":"date","description":"Only return workouts after this date (YYYY-MM-DD)."}}},"outputSchema":{"type":"object","properties":{"workouts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"started_at":{"type":"string"},"completed_at":{"type":["string","null"]},"duration_seconds":{"type":["number","null"]},"total_volume":{"type":["number","null"]},"total_sets":{"type":["number","null"]},"template_name":{"type":["string","null"]},"exercises":{"type":"array","items":{"type":"object","properties":{"exercise_id":{"type":"string"},"exercise_name":{"type":"string"},"primary_muscle":{"type":"string"},"exercise_type":{"type":"string"},"sort_order":{"type":"number"},"sets":{"type":"array","items":{"type":"object","properties":{"set_number":{"type":"number"},"set_type":{"type":"string"},"weight_kg":{"type":["number","null"],"description":"Loaded weight for free-weight sets. NULL for cable sets — see plates_count / actual_kg instead."},"reps":{"type":["number","null"]},"duration_seconds":{"type":["number","null"]},"distance_meters":{"type":["number","null"]},"rpe":{"type":["number","null"]},"rir":{"type":["number","null"]},"is_completed":{"type":"boolean"},"plates_count":{"type":["number","null"],"description":"Bilateral cable set: number of plates on the pulley. NULL for free-weight sets."},"plates_left":{"type":["number","null"],"description":"Unilateral cable set: plates loaded for the left side. Paired with plates_right."},"plates_right":{"type":["number","null"],"description":"Unilateral cable set: plates loaded for the right side. Paired with plates_left."},"equipment_item_id":{"type":["string","null"],"description":"UUID of the cable machine used (workouts.equipment_items). Set on cable sets only."},"pulley_name":{"type":["string","null"],"description":"Name of the pulley position on the machine (e.g. 'Front Press', 'Butterfly'). Set on cable sets only."},"actual_kg":{"type":["number","null"],"description":"Server-resolved kg for bilateral cable sets — base_weight + plates_count*weight_per_block from the equipment's pulley config. NULL for free-weight sets and unilateral cable sets."},"actual_kg_left":{"type":["number","null"],"description":"Server-resolved kg for the left side of a unilateral cable set. NULL otherwise."},"actual_kg_right":{"type":["number","null"],"description":"Server-resolved kg for the right side of a unilateral cable set. NULL otherwise."}}}}}}}}}},"count":{"type":"number"},"has_more":{"type":"boolean"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/exercises/list","method":"GET","description":"Search and browse the exercise catalog. Use when a user asks about available exercises — e.g. 'What chest exercises do I have?' or 'Show me cable exercises'. Returns both system (built-in) and user-created exercises. Supports text search, muscle group filtering, and equipment type filtering.","inputSchema":{"type":"object","properties":{"search":{"type":"string","description":"Text search on exercise name (case-insensitive partial match)."},"muscle_group":{"type":"string","enum":["chest","back","shoulders","biceps","triceps","forearms","quadriceps","hamstrings","glutes","calves","core","full_body"],"description":"Filter by primary muscle group."},"exercise_type":{"type":"string","enum":["barbell","dumbbell","machine","cable","bodyweight","kettlebell","band","cardio","other"],"description":"Filter by exercise type."},"equipment_type":{"type":"string","enum":["cable_machine","dumbbell","kettlebell","barbell","bodyweight","machine_other","none"],"description":"Filter by equipment type."},"include_archived":{"type":"boolean","description":"Include archived exercises. Default: false."},"limit":{"type":"number","description":"Max results (1-100, default 50)."},"offset":{"type":"number","description":"Pagination offset (default 0)."}}},"outputSchema":{"type":"object","properties":{"exercises":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"primary_muscle":{"type":"string"},"secondary_muscles":{"type":"array","items":{"type":"string"}},"exercise_type":{"type":"string"},"measurement_type":{"type":"string"},"laterality":{"type":"string"},"equipment_type":{"type":["string","null"]},"cable_pulley_position":{"type":["string","null"]},"required_apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]}}},"description":"Apparatus types this exercise can be performed with. OR semantics — at least ONE must be available at the gym."},"is_system":{"type":"boolean"},"is_archived":{"type":"boolean"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/exercises/:id","method":"GET","description":"Get full details for a single exercise by ID. Use when you need complete exercise information including instructions, video URL, and all metadata.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]},"primary_muscle":{"type":"string"},"secondary_muscles":{"type":"array","items":{"type":"string"}},"exercise_type":{"type":"string"},"measurement_type":{"type":"string"},"weight_unit":{"type":"string"},"equipment":{"type":["string","null"]},"instructions":{"type":["string","null"]},"video_url":{"type":["string","null"]},"laterality":{"type":"string"},"equipment_type":{"type":["string","null"]},"cable_pulley_position":{"type":["string","null"]},"required_apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]}}},"description":"Apparatus types this exercise can be performed with (OR semantics)."},"is_system":{"type":"boolean"},"is_archived":{"type":"boolean"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/exercises/:id/history","method":"GET","description":"Get per-session workout history for a single exercise, with server-side 1RM estimation (Epley), volume aggregation, and cable plate-to-kg resolution. Includes a personal best aggregate computed across all-time history regardless of the range filter. Use for exercise detail charts (1RM trend, volume trend) and recent sessions list.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Exercise UUID (path parameter)."},"range":{"type":"string","enum":["1m","3m","1y","all"],"description":"Time range filter for sessions. Default: 3m."},"gym_id":{"type":"string","description":"Optional gym UUID to filter sessions by gym."}},"required":["id"]},"outputSchema":{"type":"object","properties":{"exercise_id":{"type":"string"},"range":{"type":"string"},"sessions":{"type":"array","items":{"type":"object","properties":{"workout_id":{"type":"string"},"workout_name":{"type":"string"},"performed_at":{"type":"string"},"gym_id":{"type":["string","null"]},"gym_name":{"type":["string","null"]},"sets":{"type":"array","items":{"type":"object","properties":{"set_number":{"type":"number"},"weight_kg":{"type":"number"},"reps":{"type":"number"},"rpe":{"type":["number","null"]}}}},"top_set":{"type":"object","properties":{"weight_kg":{"type":"number"},"reps":{"type":"number"}}},"total_volume_kg":{"type":"number"},"estimated_1rm_kg":{"type":"number"},"total_sets":{"type":"number"},"total_reps":{"type":"number"}}}},"personal_best":{"type":["object","null"],"properties":{"top_set":{"type":"object","properties":{"weight_kg":{"type":"number"},"reps":{"type":"number"},"performed_at":{"type":"string"}}},"estimated_1rm_kg":{"type":"number"},"max_volume_session_kg":{"type":"number"}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/exercises/:id/context","method":"GET","description":"Get programming and scheduling context for an exercise. Returns which programs include this exercise (with target sets/reps/RPE), the next scheduled workout containing it, the last time it was performed, and whether the user's active gym has the required equipment.","inputSchema":{"type":"object","properties":{"id":{"type":"string","description":"Exercise UUID (path parameter)."}},"required":["id"]},"outputSchema":{"type":"object","properties":{"exercise_id":{"type":"string"},"in_programs":{"type":"array","items":{"type":"object","properties":{"program_id":{"type":"string"},"program_name":{"type":"string"},"target_sets":{"type":["number","null"]},"target_reps":{"type":["string","null"]},"target_rpe":{"type":["number","null"]},"target_weight_kg":{"type":"null"},"notes":{"type":["string","null"]}}}},"next_scheduled":{"type":["object","null"],"properties":{"workout_id":{"type":"string"},"scheduled_for":{"type":"string"},"program_name":{"type":"string"}}},"last_performed":{"type":["object","null"],"properties":{"workout_id":{"type":"string"},"performed_at":{"type":"string"}}},"active_gym_equipment_available":{"type":"boolean"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/exercises/create","method":"POST","description":"Create a custom exercise in the user's exercise catalog. Use when a user wants to add an exercise that doesn't exist in the system catalog — e.g. 'Add a Bulgarian split squat' or 'Create a custom cable fly variation'. The exercise can then be used in programs and workout logging.","inputSchema":{"type":"object","required":["name","primary_muscle","exercise_type"],"properties":{"name":{"type":"string","description":"Exercise name."},"description":{"type":"string","description":"Optional description."},"primary_muscle":{"type":"string","enum":["chest","back","shoulders","biceps","triceps","forearms","quadriceps","hamstrings","glutes","calves","core","full_body"]},"secondary_muscles":{"type":"array","items":{"type":"string"},"description":"Secondary muscle groups."},"exercise_type":{"type":"string","enum":["barbell","dumbbell","machine","cable","bodyweight","kettlebell","band","cardio","other"]},"measurement_type":{"type":"string","enum":["weight_reps","reps_only","duration","distance"],"description":"Default: weight_reps."},"weight_unit":{"type":"string","enum":["kg","lbs","plates"],"description":"Default: kg."},"equipment":{"type":"string","description":"Free-text equipment description."},"instructions":{"type":"string","description":"How to perform the exercise."},"laterality":{"type":"string","enum":["bilateral","unilateral","either"],"description":"Default: bilateral."},"equipment_type":{"type":"string","enum":["cable_machine","dumbbell","kettlebell","barbell","bodyweight","machine_other","none"]},"cable_pulley_position":{"type":"string","description":"Pulley position (only for cable_machine equipment_type)."},"apparatus_type_ids":{"type":"array","items":{"type":"string"},"description":"Optional. Apparatus types required to perform this exercise — UUIDs OR slugs (e.g. ['flat_bench','adjustable_bench']). When multiple are provided, the meaning is OR (any one satisfies the requirement). Use fitness_apparatus_types_list to discover slugs."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"primary_muscle":{"type":"string"},"exercise_type":{"type":"string"},"laterality":{"type":"string"},"equipment_type":{"type":["string","null"]},"required_apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/exercises/:id","method":"PATCH","description":"Update a user-created exercise. Cannot modify system exercises. Use to correct exercise details, change muscle groups, or archive/unarchive.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":["string","null"]},"primary_muscle":{"type":"string"},"secondary_muscles":{"type":"array","items":{"type":"string"}},"exercise_type":{"type":"string"},"measurement_type":{"type":"string"},"weight_unit":{"type":"string"},"equipment":{"type":["string","null"]},"instructions":{"type":["string","null"]},"laterality":{"type":"string","enum":["bilateral","unilateral","either"]},"equipment_type":{"type":["string","null"]},"cable_pulley_position":{"type":["string","null"]},"is_archived":{"type":"boolean"},"apparatus_type_ids":{"type":"array","items":{"type":"string"},"description":"If provided, REPLACES the existing apparatus requirements for this exercise. UUIDs or slugs accepted."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"primary_muscle":{"type":"string"},"exercise_type":{"type":"string"},"is_archived":{"type":"boolean"},"required_apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/gyms/list","method":"GET","description":"List all gyms the user owns or has been shared access to, with equipment counts. Use when a user asks 'What gyms do I have set up?' or needs to choose a gym. Returns gyms sorted with the default gym first. Each gym includes is_owner to distinguish owned vs shared gyms.","outputSchema":{"type":"object","properties":{"gyms":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"location_type":{"type":"string"},"is_default":{"type":"boolean"},"equipment_count":{"type":"number"},"is_owner":{"type":"boolean","description":"True if the authenticated user owns this gym."},"created_at":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/gyms/create","method":"POST","description":"Create a new gym. Use when a user wants to register a place they work out — e.g. 'Add my home gym' or 'Set up World Class as a gym'. After creation, add equipment to it.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Gym name."},"location_type":{"type":"string","enum":["home","commercial","outdoor","other"],"description":"Default: home."},"is_default":{"type":"boolean","description":"Make this the default gym. Default: false."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"location_type":{"type":"string"},"is_default":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/gyms/:id","method":"GET","description":"Get full details for a gym including all its equipment. Use when a user asks about a specific gym's setup or inventory.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"location_type":{"type":"string"},"is_default":{"type":"boolean"},"equipment":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"equipment_type":{"type":"string"},"weight_config":{"type":"object"},"notes":{"type":["string","null"]}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/gyms/:id","method":"PATCH","description":"Update a gym's name, location type, or default status. Setting is_default to true automatically clears default from other gyms.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"location_type":{"type":"string","enum":["home","commercial","outdoor","other"]},"is_default":{"type":"boolean"}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"location_type":{"type":"string"},"is_default":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/gyms/:id","method":"DELETE","description":"Delete a gym and all its equipment. Also clears active_gym_id if this gym was active. Use with caution — this is irreversible.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/gyms/active","method":"GET","description":"Get the user's currently active gym with all its equipment. Use when suggesting weights or equipment — the active gym determines what equipment is available for the current workout session. Each equipment row includes apparatus_types and configurations for non-weighted apparatus equipment (benches, racks, etc.). Returns null gym if no active gym is set.","outputSchema":{"type":"object","properties":{"gym":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"location_type":{"type":"string"},"is_default":{"type":"boolean"}}},"equipment":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"equipment_type":{"type":"string","description":"One of cable_machine, dumbbell, kettlebell, barbell, machine_other, apparatus."},"weight_config":{"type":["object","null"],"description":"Null for apparatus rows; otherwise type-specific shape."},"configurations":{"type":["object","null"],"description":"Apparatus-specific settings; null for non-apparatus."},"apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]}}},"description":"Apparatus types this equipment provides; empty array for non-apparatus rows."},"notes":{"type":["string","null"]}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/gyms/active","method":"POST","description":"Set or clear the active gym. Use when a user says 'I'm at the gym' or 'Switch to my home gym'. Pass gym_id to set, or null to clear. Returns the updated active gym with equipment.","inputSchema":{"type":"object","required":["gym_id"],"properties":{"gym_id":{"type":["string","null"],"description":"Gym UUID to set as active, or null to clear."}}},"outputSchema":{"type":"object","properties":{"gym":{"type":["object","null"]},"equipment":{"type":"array"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/gyms/:id/members","method":"GET","description":"List all members of a gym, including the owner. Use when a user asks 'Who has access to my gym?' or to see who shares a gym. The owner is always included with role 'owner'.","outputSchema":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Membership ID (absent for the owner entry)."},"user_id":{"type":"string"},"display_name":{"type":["string","null"]},"role":{"type":"string","enum":["owner","member"]},"created_at":{"type":"string"}}}},"total":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/gyms/:id/members","method":"POST","description":"Share a gym with another user by adding them as a member. Only the gym owner can do this. Use when a user says 'Share my gym with [person]' or 'Give [person] access to my home gym'. If the user is already a member, the existing membership is returned.","inputSchema":{"type":"object","required":["user_id"],"properties":{"user_id":{"type":"string","description":"UUID of the user to share the gym with."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"gym_id":{"type":"string"},"user_id":{"type":"string"},"role":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/gyms/:id/members/:userId","method":"DELETE","description":"Remove a member from a gym, revoking their shared access. Only the gym owner can do this. Use when a user says 'Remove [person] from my gym' or 'Stop sharing my gym with [person]'.","outputSchema":{"type":"object","description":"Empty response with 204 status on success."},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/equipment/list","method":"GET","description":"List all equipment at a specific gym. Use when a user asks 'What dumbbells do I have at home?' or to check available weights for an exercise at a specific location.","inputSchema":{"type":"object","required":["gym_id"],"properties":{"gym_id":{"type":"string","description":"UUID of the gym to list equipment for."}}},"outputSchema":{"type":"object","properties":{"equipment":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"equipment_type":{"type":"string","description":"One of cable_machine, dumbbell, kettlebell, barbell, machine_other, apparatus."},"weight_config":{"type":["object","null"],"description":"Null for apparatus rows. Otherwise type-specific (discrete_set or cable_machine shape)."},"configurations":{"type":["object","null"],"description":"Apparatus-specific settings JSONB; null for non-apparatus equipment."},"apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]}}},"description":"Apparatus types this equipment provides (populated for apparatus rows; empty array otherwise)."},"quantity":{"type":"integer","description":"Number of this equipment available (default 1)."},"notes":{"type":["string","null"]},"summary":{"type":"string","description":"Human-readable one-line summary."}}}},"total":{"type":"integer"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/apparatus-types/list","method":"GET","description":"List all apparatus types in the reference catalog (benches, racks, bars, plyo boxes, etc.). Use BEFORE creating apparatus equipment or tagging exercises with apparatus requirements — these are the canonical slug values that fitness_equipment_create#apparatus_type_ids and fitness_exercises_create#apparatus_type_ids accept. The list is bounded (currently 10 entries) and rarely changes.","outputSchema":{"type":"object","properties":{"apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"UUID — stable identifier for joins."},"slug":{"type":"string","description":"Stable machine identifier (e.g. 'hyperextension_bench'). Treat as immutable."},"name":{"type":"string","description":"Human-readable display name."},"description":{"type":["string","null"]}}}},"total":{"type":"integer"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/equipment/create","method":"POST","description":"Add equipment to a gym. Use when a user says 'I have dumbbells from 5-30kg at home', 'Add the cable machine at my gym', or 'Register my hyperextension bench'. For weighted equipment (dumbbell/kettlebell/barbell/cable_machine/machine_other) provide weight_config. For non-weighted apparatus (benches, racks, bars, plyo boxes — equipment_type='apparatus') OMIT weight_config and provide apparatus_type_ids — a list of apparatus_types UUIDs OR slugs (e.g. ['hyperextension_bench'] or mixing UUIDs and slugs is fine). One physical piece of equipment can declare multiple apparatus types, e.g. a Reebok Deck might be ['adjustable_bench','aerobic_step','flat_bench']. Use fitness_apparatus_types_list to discover the available slugs.","inputSchema":{"type":"object","required":["gym_id","name","equipment_type"],"properties":{"gym_id":{"type":"string","description":"UUID of the gym to add equipment to."},"name":{"type":"string","description":"Equipment name (e.g. 'Adjustable Dumbbells', 'Cable Tower', 'Taurus B800 Hyperextension Bench')."},"equipment_type":{"type":"string","enum":["cable_machine","dumbbell","kettlebell","barbell","machine_other","apparatus"]},"weight_config":{"type":["object","null"],"description":"REQUIRED for non-apparatus types; MUST be omitted/null when equipment_type='apparatus'. Shape depends on type: discrete_set={type, unit, available_weights[]} or cable_machine={type, unit, block_count, pulley_positions[{name, weight_per_block, base_weight?}]}. base_weight is the cable/attachment weight before any blocks (default 0)."},"configurations":{"type":["object","null"],"description":"Optional apparatus-specific settings JSONB. Only meaningful when equipment_type='apparatus'. Examples: Reebok Deck → {step_heights_cm:[20,35]}; Taurus B800 → {incline_positions:['flat','45deg'], max_user_weight_kg:150}."},"apparatus_type_ids":{"type":"array","items":{"type":"string"},"description":"REQUIRED (≥1 entry) when equipment_type='apparatus'; MUST be empty/omitted otherwise. Each entry is either an apparatus_types UUID or a slug (e.g. 'flat_bench'). Mixed UUID/slug arrays are accepted. Use fitness_apparatus_types_list to discover slugs."},"quantity":{"type":"integer","description":"Number of this equipment available (default 1, must be >= 1)."},"notes":{"type":"string","description":"Optional notes about the equipment."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"equipment_type":{"type":"string"},"weight_config":{"type":["object","null"]},"configurations":{"type":["object","null"]},"apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"description":{"type":["string","null"]}}},"description":"Apparatus types this equipment provides (populated for apparatus rows; empty array otherwise)."},"quantity":{"type":"integer"},"gym_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/equipment/:id","method":"PATCH","description":"Update equipment details, weight configuration, apparatus tagging, or apparatus-specific configurations. Use when a user adds new weights, retags an apparatus item, or changes equipment setup. When apparatus_type_ids is provided it REPLACES the existing tags atomically.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"equipment_type":{"type":"string","enum":["cable_machine","dumbbell","kettlebell","barbell","machine_other","apparatus"]},"weight_config":{"type":["object","null"],"description":"Updated weight configuration. Pass null when changing equipment_type to 'apparatus'."},"configurations":{"type":["object","null"],"description":"Apparatus-specific settings JSONB. Only meaningful for apparatus equipment."},"apparatus_type_ids":{"type":"array","items":{"type":"string"},"description":"If provided, replaces ALL existing apparatus tags for this equipment. Each entry is a UUID or a slug. Only valid when equipment_type='apparatus' (effective)."},"quantity":{"type":"integer","description":"Number of this equipment available (must be >= 1)."},"notes":{"type":["string","null"]}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"equipment_type":{"type":"string"},"weight_config":{"type":["object","null"]},"configurations":{"type":["object","null"]},"apparatus_types":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}},"quantity":{"type":"integer"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/workouts/schedule","method":"POST","description":"Schedule a workout program in one of two modes. (1) ONE-OFF MODE: pass `dates` — an array of explicit YYYY-MM-DD entries (with optional starts_at/ends_at time slots). Creates concrete scheduled workout rows with snapshotted exercises from the program template; subsequent template edits do NOT change already-scheduled workouts. Use when the user names specific dates (e.g. 'schedule Push Day for Monday and Wednesday'). (2) RECURRING MODE: pass `recurrence` — `{ start_date, weeks_to_generate, days_of_week }`. Inserts a single workout_schedules row; the database trigger then materializes child task rows for each matching weekday in the window. Use when the user says 'every Monday, Wednesday, Friday for the next 4 weeks'. IMPORTANT: `recurrence.days_of_week` uses Postgres EXTRACT(DOW) convention — Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5, Saturday=6. Provide EXACTLY ONE of `dates` or `recurrence`, not both. The created workouts/tasks appear in fitness_workouts_upcoming.","inputSchema":{"type":"object","required":["program_id"],"properties":{"program_id":{"type":"string","description":"UUID of the program template to schedule."},"dates":{"type":"array","minItems":1,"maxItems":90,"items":{"type":"object","required":["date"],"properties":{"date":{"type":"string","format":"date","description":"Date to schedule the workout (YYYY-MM-DD). Must not be in the past."},"starts_at":{"type":"string","description":"Optional start time (HH:MM). Must pair with ends_at."},"ends_at":{"type":"string","description":"Optional end time (HH:MM). Must be after starts_at."}}},"description":"ONE-OFF MODE: array of explicit dates (and optional time slots) to schedule the program on. Mutually exclusive with `recurrence`."},"recurrence":{"type":"object","required":["start_date","weeks_to_generate","days_of_week"],"properties":{"start_date":{"type":"string","format":"date","description":"First day of the recurring window (YYYY-MM-DD). Must not be in the past."},"weeks_to_generate":{"type":"integer","minimum":1,"maximum":52,"description":"How many weeks of occurrences to generate (1-52)."},"days_of_week":{"type":"array","minItems":1,"maxItems":7,"items":{"type":"integer","minimum":0,"maximum":6},"description":"Days of the week to schedule on, using Postgres EXTRACT(DOW) convention: Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4, Friday=5, Saturday=6. Example: [1,3,5] = Mon/Wed/Fri."},"workout_name":{"type":"string","description":"Optional override for the workout name. Defaults to the program template name."}},"description":"RECURRING MODE: schedule on every matching weekday for the next N weeks. Mutually exclusive with `dates`."},"notes":{"type":"string","description":"Optional notes applied to all created workouts (one-off mode only)."}}},"outputSchema":{"type":"object","properties":{"mode":{"type":"string","enum":["one_off","recurring"],"description":"Which mode the request was processed in."},"created":{"type":"array","items":{"type":"object","properties":{"workout_id":{"type":"string"},"scheduled_for":{"type":"string","format":"date"}}},"description":"ONE-OFF MODE: list of created workout rows."},"count":{"type":"number","description":"ONE-OFF MODE: number of workouts created."},"schedule_id":{"type":"string","description":"RECURRING MODE: id of the inserted workout_schedules row."},"start_date":{"type":"string","format":"date","description":"RECURRING MODE: window start date."},"end_date":{"type":"string","format":"date","description":"RECURRING MODE: window end date (start_date + weeks_to_generate*7 - 1)."},"expected_occurrences":{"type":"integer","description":"RECURRING MODE: number of occurrences the trigger is expected to generate within the window."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/workouts/start","method":"POST","description":"Start a workout. If a program_id is provided, the start flow checks sources in order before creating a fresh workout: (1a) a one-off scheduled workout for this template with scheduled_for=today (consumed_scheduled=true, consumed_overdue=false); (1b) else the EARLIEST overdue scheduled workout for this template within the last 14 days, oldest first (consumed_scheduled=true, consumed_overdue=true) — the 14-day cap prevents resurrecting ancient stale rows when the user returns to a program after a long break; (2) else an active recurring workout_schedules row for this template where today is in [start_date, end_date] and today's day-of-week is in days_of_week (consumed_recurring=true; materializes a new workouts row with schedule_id set and the matching `schedule:<id>:<today>` task is moved to in_progress); (3) fallback — create a fresh workout from the template. Day-of-week values use Postgres EXTRACT(DOW) (Sunday=0..Saturday=6). For ad-hoc workouts (no program_id), a name is required. Always returns the workout with its exercises.","inputSchema":{"type":"object","properties":{"program_id":{"type":"string","description":"UUID of the program template. If provided, checks for a matching scheduled workout to consume before creating a new one."},"name":{"type":"string","description":"Workout name. Required for ad-hoc workouts (no program_id). Optional when starting from a program (defaults to program name)."},"notes":{"type":"string","description":"Optional workout notes."}}},"outputSchema":{"type":"object","properties":{"workout_id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"started_at":{"type":"string"},"template_id":{"type":["string","null"]},"consumed_scheduled":{"type":"boolean","description":"True if an existing one-off scheduled workout was consumed instead of creating a new one."},"consumed_overdue":{"type":"boolean","description":"True if the consumed scheduled workout was overdue (scheduled_for < today). Only relevant when consumed_scheduled is true."},"consumed_recurring":{"type":"boolean","description":"True if today's occurrence of an active recurring schedule was materialized into a workout. Mutually exclusive with consumed_scheduled."},"exercises":{"type":"array","items":{"type":"object","properties":{"exercise_id":{"type":"string"},"sort_order":{"type":"number"},"target_sets":{"type":["number","null"]},"target_reps_min":{"type":["number","null"]},"target_reps_max":{"type":["number","null"]},"target_rpe":{"type":["number","null"]}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/workouts/:workoutId/schedule","method":"PATCH","description":"Reschedule a single scheduled workout — change its date or time slot. Use when a user says 'move my Monday workout to Tuesday' or 'change the time of my scheduled push day'. Only works on workouts with status='scheduled'. Returns 409 if the workout is in_progress, completed, or cancelled.","inputSchema":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"New date (YYYY-MM-DD). Must not be in the past."},"starts_at":{"type":"string","description":"New start time (HH:MM). Must pair with ends_at."},"ends_at":{"type":"string","description":"New end time (HH:MM). Must be after starts_at."},"clear_time":{"type":"boolean","description":"If true, removes the time slot (sets starts_at and ends_at to null)."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"scheduled_for":{"type":"string","format":"date"},"scheduled_starts_at":{"type":["string","null"]},"scheduled_ends_at":{"type":["string","null"]},"status":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/workouts/:workoutId/schedule","method":"DELETE","description":"Cancel a scheduled workout by permanently deleting it and its snapshotted exercises. Use when a user says 'cancel my scheduled workout' or 'remove that workout from my calendar'. Only works on workouts with status='scheduled'. Returns 409 if the workout has already been started or completed. This is irreversible.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"},"workout_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/equipment/:id","method":"DELETE","description":"Delete a piece of equipment from a gym. Use when equipment is no longer available. This is irreversible.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"},"id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/workouts/:workoutId","method":"GET","description":"Get full details for a single workout including all exercises, their sets, and substitution metadata. Shows both active and replaced exercises — replaced ones have is_active=false and replaced_by_id set. Use to inspect the current state of an in-progress workout or review a completed workout.","inputSchema":{"type":"object","properties":{"workoutId":{"type":"string","description":"Workout UUID (path parameter)."}},"required":["workoutId"]},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/fitness/workouts/:workoutId/exercises/:workoutExerciseId/swap","method":"POST","description":"Swap an exercise mid-workout. Replaces one exercise with another while preserving already-completed sets on the original. The template/program is NOT modified — the swap only affects this workout instance. Use when a user reports pain, equipment is unavailable, or they want an alternative exercise targeting the same muscle group.","inputSchema":{"type":"object","properties":{"workoutId":{"type":"string","description":"Workout UUID (path parameter). Must be in_progress."},"workoutExerciseId":{"type":"string","description":"The workout_exercise UUID to replace (path parameter)."},"new_exercise_id":{"type":"string","description":"The exercise catalog UUID to swap in."},"reason":{"type":"string","description":"Optional reason for the swap (e.g. 'Pain/discomfort', 'Equipment unavailable')."}},"required":["workoutId","workoutExerciseId","new_exercise_id"]},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"fitness"},{"path":"/api/v1/fitness/workouts/:workoutId/exercises/:workoutExerciseId/suggest-swap","method":"GET","description":"Suggest substitute exercises for a mid-workout swap. Returns up to 10 exercises targeting the same primary muscle group as the exercise being replaced. User-created exercises are listed first, then system exercises. Call this BEFORE fitness_workouts_exercises_swap to help the user choose a replacement.","inputSchema":{"type":"object","properties":{"workoutId":{"type":"string","description":"Workout UUID (path parameter)."},"workoutExerciseId":{"type":"string","description":"The workout_exercise UUID to find replacements for (path parameter)."},"equipment_type":{"type":"string","enum":["cable_machine","dumbbell","kettlebell","barbell","bodyweight","machine_other","none"],"description":"Optional filter to narrow suggestions to a specific equipment type."},"exclude_exercise_ids":{"type":"string","description":"Optional comma-separated list of exercise UUIDs to exclude from suggestions (e.g. exercises already in this workout)."}},"required":["workoutId","workoutExerciseId"]},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"fitness"},{"path":"/api/v1/notes/search","method":"GET","description":"Full-text search across all notes using PostgreSQL tsvector. Use when a user wants to find notes by keyword — for example 'find my notes about Iceland' or 'search for meeting notes'. Returns ranked results with content snippets highlighting the matched terms. Title matches rank higher than content matches. Supports filtering by notebook and archive status.","inputSchema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"Search terms. Words are ANDed together with prefix matching (e.g. 'meet island' matches 'meeting in Iceland')."},"notebook_id":{"type":"string","description":"Optional UUID to limit search to a specific notebook."},"tag":{"type":"string","description":"Filter by tag name (case-insensitive exact match)."},"is_archived":{"type":"boolean","description":"Whether to search archived notes. Defaults to false (active notes only)."},"limit":{"type":"number","description":"Maximum results to return (default: 20, max: 50)."},"offset":{"type":"number","description":"Pagination offset (default: 0)."}}},"outputSchema":{"type":"object","properties":{"results":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"snippet":{"type":"string","description":"Content excerpt with matched terms highlighted."},"notebook":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"tags":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"color":{"type":["string","null"]}}}},"is_pinned":{"type":"boolean"},"is_archived":{"type":"boolean"},"updated_at":{"type":"string"}}}},"count":{"type":"number"},"has_more":{"type":"boolean"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"notes"},{"path":"/api/v1/notes/:note_id","method":"GET","description":"Get a single note with its full Markdown content, notebook info, tags, and attachment count. Use when a user asks about a specific note or wants to read its content — for example 'show me that note about the API design' or 'what does my grocery list say?'.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"content":{"type":"string","description":"Full Markdown content of the note."},"notebook":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"tags":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"color":{"type":["string","null"]}}}},"attachment_count":{"type":"number"},"is_pinned":{"type":"boolean"},"is_archived":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"url":{"type":"string","description":"Direct link to the note in the Notes app."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"notes"},{"path":"/api/v1/notes","method":"GET","description":"List notes with filtering, sorting, and pagination. Returns summaries with a ~200-character content preview rather than full content. Use when a user asks to see their notes — for example 'show my pinned notes', 'what's in my Work notebook?', or 'list my recent notes'. Supports filtering by notebook, tag, pinned status, and archive status.","inputSchema":{"type":"object","properties":{"notebook_id":{"type":"string","description":"Filter to notes in a specific notebook (UUID)."},"tag":{"type":"string","description":"Filter by tag name (exact match, case-insensitive)."},"is_pinned":{"type":"boolean","description":"If true, only pinned notes. If false, only unpinned."},"is_archived":{"type":"boolean","description":"Whether to show archived notes. Defaults to false (active notes only)."},"sort":{"type":"string","enum":["updated_at","created_at","title"],"description":"Sort field. Defaults to updated_at."},"order":{"type":"string","enum":["asc","desc"],"description":"Sort direction. Defaults to desc."},"limit":{"type":"number","description":"Maximum notes to return (default: 50, max: 100)."},"offset":{"type":"number","description":"Pagination offset (default: 0)."}}},"outputSchema":{"type":"object","properties":{"notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"preview":{"type":"string","description":"First ~200 characters of content."},"notebook":{"type":["object","null"],"properties":{"id":{"type":"string"},"name":{"type":"string"}}},"tags":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"color":{"type":["string","null"]}}}},"attachment_count":{"type":"number"},"is_pinned":{"type":"boolean"},"is_archived":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}},"count":{"type":"number"},"has_more":{"type":"boolean"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"notes"},{"path":"/api/v1/notes/notebooks","method":"GET","description":"Get all notebooks as a nested tree structure with note counts. Use when a user asks about their notebook organization — for example 'what notebooks do I have?' or 'show me my note categories'. Returns a hierarchical tree where each notebook includes its children and the number of notes it contains.","outputSchema":{"type":"object","properties":{"notebooks":{"type":"array","description":"Top-level notebooks with nested children.","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":["string","null"]},"sort_order":{"type":"number"},"note_count":{"type":"number"},"children":{"type":"array","description":"Child notebooks (recursive structure).","items":{"type":"object"}}}}},"total_notebooks":{"type":"number"},"total_notes":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"notes"},{"path":"/api/v1/notes/create","method":"POST","description":"Create a new note with optional content, notebook placement, and tags. Use when a user wants to save information — for example 'save a note about the meeting' or 'create a note in my Work notebook'. Tags are matched by name (case-insensitive) and created automatically if they don't exist.","inputSchema":{"type":"object","properties":{"title":{"type":"string","description":"Note title. Defaults to 'Untitled' if not provided."},"content":{"type":"string","description":"Markdown content of the note."},"notebook_id":{"type":"string","description":"UUID of the notebook to place the note in."},"tags":{"type":"array","items":{"type":"string"},"description":"Tag names to assign. Tags are created automatically if they don't exist."}}},"outputSchema":{"type":"object","properties":{"note_id":{"type":"string"},"title":{"type":"string"},"notebook_id":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"},"description":"Names of assigned tags."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notes"},{"path":"/api/v1/notes/:note_id","method":"PATCH","description":"Update an existing note's title, content, notebook, pinned/archived status, or tags. Only provided fields are changed — omitted fields stay as-is. Use when a user wants to edit a note — for example 'rename that note', 'move it to my Work notebook', 'pin that note', or 'update the content'. When tags are provided, they replace all existing tags on the note.","inputSchema":{"type":"object","properties":{"title":{"type":"string","description":"New title for the note."},"content":{"type":"string","description":"New Markdown content (replaces existing content entirely)."},"notebook_id":{"type":["string","null"],"description":"Move to a different notebook (UUID), or null to remove from notebook."},"is_pinned":{"type":"boolean","description":"Pin or unpin the note."},"is_archived":{"type":"boolean","description":"Archive or unarchive the note."},"tags":{"type":"array","items":{"type":"string"},"description":"Replacement tag names. Replaces all existing tags. Tags are created automatically if they don't exist."}}},"outputSchema":{"type":"object","properties":{"note_id":{"type":"string"},"title":{"type":"string"},"updated_fields":{"type":"array","items":{"type":"string"},"description":"List of field names that were changed."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notes"},{"path":"/api/v1/notes/append/:note_id","method":"POST","description":"Append content to an existing note, separated by a double newline. Use when a user wants to add to a note without replacing existing content — for example 'add this to my meeting notes' or 'append a new section to that document'. This is safer than PATCH for incremental additions because it doesn't require knowing the current content.","inputSchema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"Markdown content to append. Will be separated from existing content by two newlines."}}},"outputSchema":{"type":"object","properties":{"note_id":{"type":"string"},"title":{"type":"string"},"content_length":{"type":"number","description":"Total length of the note content after appending."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notes"},{"path":"/api/v1/notes/:note_id","method":"DELETE","description":"Permanently delete a note and all its attachments. This is a hard delete — the note cannot be recovered. Consider using PATCH to set is_archived=true for soft deletion instead. Use only when a user explicitly asks to permanently delete a note.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"},"note_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notes"},{"path":"/api/v1/notes/notebooks/create","method":"POST","description":"Create a new notebook for organizing notes. Notebooks can be nested inside other notebooks using parent_id. Use when a user wants to organize their notes — for example 'create a Work notebook' or 'add a sub-notebook for Q1 inside Projects'.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Notebook name."},"parent_id":{"type":"string","description":"UUID of the parent notebook for nesting. Omit for a top-level notebook."}}},"outputSchema":{"type":"object","properties":{"notebook_id":{"type":"string"},"name":{"type":"string"},"parent_id":{"type":["string","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notes"},{"path":"/api/v1/security/scans","method":"POST","description":"Ingest a new security scan result. Called by GitHub Actions after a security audit run completes. Stores the full scan with findings, summary counts, and run metadata. Use this to record the results of periodic audits, PR gate checks, or manual security scans.","inputSchema":{"type":"object","required":["scan_type","status","summary","findings"],"properties":{"scan_type":{"type":"string","enum":["periodic_audit","pr_gate","manual"],"description":"The type of scan that produced these results."},"status":{"type":"string","enum":["pass","fail","partial"],"description":"Overall scan result."},"summary":{"type":"object","description":"Severity counts and check totals. E.g. { critical: 1, high: 2, checks_passed: 5, checks_failed: 2 }."},"findings":{"type":"array","description":"Array of finding objects with id, category, severity, title, description, and optional remediation.","items":{"type":"object","properties":{"id":{"type":"string"},"category":{"type":"string"},"severity":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"remediation":{"type":"string"},"source_check":{"type":"string"},"raw_detail":{"type":"string"}}}},"run_id":{"type":"string","description":"GitHub Actions run ID."},"run_url":{"type":"string","description":"Link to the GitHub Actions run."},"branch":{"type":"string","description":"Git branch."},"commit_sha":{"type":"string","description":"Git commit SHA."},"raw_output":{"type":"string","description":"Full raw output from the scan (optional)."},"scan_started_at":{"type":"string","description":"ISO 8601 timestamp when scan started."},"scan_completed_at":{"type":"string","description":"ISO 8601 timestamp when scan completed."}}},"outputSchema":{"type":"object","description":"The created scan record with all fields.","properties":{"id":{"type":"string"},"scan_type":{"type":"string"},"status":{"type":"string"},"summary":{"type":"object"},"findings":{"type":"array"},"run_id":{"type":["string","null"]},"run_url":{"type":["string","null"]},"branch":{"type":["string","null"]},"commit_sha":{"type":["string","null"]},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"security"},{"path":"/api/v1/security/scans","method":"GET","description":"List recent security scan results with optional filters. Returns summaries without full findings or raw output — use the single-scan endpoint for details. Use when reviewing scan history or checking if recent audits passed.","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Maximum scans to return (default: 10, max: 50)."},"status":{"type":"string","enum":["pass","fail","partial"],"description":"Filter by scan result."},"scan_type":{"type":"string","enum":["periodic_audit","pr_gate","manual"],"description":"Filter by scan type."},"since":{"type":"string","description":"Only scans after this ISO 8601 date."}}},"outputSchema":{"type":"object","properties":{"scans":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"scan_type":{"type":"string"},"status":{"type":"string"},"summary":{"type":"object"},"run_url":{"type":["string","null"]},"finding_count":{"type":"number"},"created_at":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"security"},{"path":"/api/v1/security/scans/:scan_id","method":"GET","description":"Get a single security scan with full details including all findings and raw output. Use when you need to review specific findings, understand remediation steps, or get the complete audit trail for a scan.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"scan_type":{"type":"string"},"status":{"type":"string"},"summary":{"type":"object"},"findings":{"type":"array"},"raw_output":{"type":["string","null"]},"run_id":{"type":["string","null"]},"run_url":{"type":["string","null"]},"branch":{"type":["string","null"]},"commit_sha":{"type":["string","null"]},"scan_started_at":{"type":["string","null"]},"scan_completed_at":{"type":["string","null"]},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"security"},{"path":"/api/v1/security/scans/latest","method":"GET","description":"Get the most recent security scan of a given type. Defaults to periodic_audit. Use as a quick check on current security posture — 'what did the last audit find?' or 'did the latest PR gate pass?'.","inputSchema":{"type":"object","properties":{"scan_type":{"type":"string","enum":["periodic_audit","pr_gate","manual"],"description":"Type of scan to fetch. Defaults to periodic_audit."}}},"outputSchema":{"type":"object","description":"The complete scan record, or null if no scans of this type exist.","properties":{"id":{"type":"string"},"scan_type":{"type":"string"},"status":{"type":"string"},"summary":{"type":"object"},"findings":{"type":"array"},"raw_output":{"type":["string","null"]},"run_id":{"type":["string","null"]},"run_url":{"type":["string","null"]},"branch":{"type":["string","null"]},"commit_sha":{"type":["string","null"]},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"security"},{"path":"/api/v1/runner/repos","method":"GET","description":"List available repositories configured for the Claude Code Runner. Each repo includes its apps (for monorepos) and metadata. Used to populate repo/app selection dropdowns in the chain creation UI.","outputSchema":{"type":"object","properties":{"repos":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"repo_url":{"type":"string"},"is_monorepo":{"type":"boolean"},"apps":{"type":"object"},"default_branch":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"settings"},{"path":"/api/v1/admin/vps/metrics","method":"GET","description":"Get current system metrics from the Ralston Suite VPS (CPU, memory, disk, network, Docker containers). Returns cached data if fetched within the last 10 seconds. Admin only.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"admin","tier":"read","app":"admin"},{"path":"/api/v1/budgeting/categories","method":"GET","description":"List variable cost categories. Each category has a label, priority tier (essential/discretionary), sort order, and status (active/archived). Filter by status with ?status=active.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["active","archived"],"description":"Filter by status. Omit for all."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/categories/create","method":"POST","description":"Create a new variable cost category.","inputSchema":{"type":"object","required":["label"],"properties":{"label":{"type":"string","description":"Category label (e.g. 'Matvörur')."},"priority_tier":{"type":"string","enum":["essential","discretionary"],"description":"Priority tier. Defaults to 'discretionary'."},"default_monthly_target":{"type":"number","description":"Default monthly budget target in ISK. Defaults to 0."},"sort_order":{"type":"integer","description":"Display sort order. Defaults to 0."},"status":{"type":"string","enum":["active","archived"],"description":"Status. Defaults to 'active'."},"account_id":{"type":"string","description":"Optional accounting key ID."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/categories/:category_id","method":"PATCH","description":"Update a variable cost category.","inputSchema":{"type":"object","properties":{"label":{"type":"string","description":"Category label."},"priority_tier":{"type":"string","enum":["essential","discretionary"]},"default_monthly_target":{"type":"number"},"sort_order":{"type":"integer"},"status":{"type":"string","enum":["active","archived"]},"account_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/categories/:category_id","method":"DELETE","description":"Delete a variable cost category.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/bank-accounts","method":"GET","description":"List bank accounts. Returns active accounts by default. Use include_closed=true for all.","inputSchema":{"type":"object","properties":{"include_closed":{"type":"boolean"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/bank-accounts","method":"POST","description":"Create a new bank account (checking, savings, cash, or other).","inputSchema":{"type":"object","required":["label"],"properties":{"label":{"type":"string","description":"Account name (e.g. 'Checking - Landsbankinn')."},"account_type":{"type":"string","enum":["checking","savings","cash","other"]},"currency":{"type":"string","description":"Currency code. Defaults to ISK."},"current_balance":{"type":"number","description":"Current balance."},"is_primary":{"type":"boolean","description":"Set as primary account."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/bank-accounts/:bank_account_id","method":"GET","description":"Get a bank account with recent balance history.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/bank-accounts/:bank_account_id","method":"PATCH","description":"Update a bank account.","inputSchema":{"type":"object","properties":{"label":{"type":"string"},"account_type":{"type":"string"},"current_balance":{"type":"number"},"is_primary":{"type":"boolean"},"status":{"type":"string","enum":["active","closed"]},"confirmation_frequency_days":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/bank-accounts/:bank_account_id","method":"DELETE","description":"Delete a bank account.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/cards","method":"GET","description":"List cards (debit and credit). Returns active cards by default.","inputSchema":{"type":"object","properties":{"include_closed":{"type":"boolean"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/cards","method":"POST","description":"Create a new card. Debit cards require linked_bank_account_id. Credit cards require billing_day and payment_due_day.","inputSchema":{"type":"object","required":["label","card_type"],"properties":{"label":{"type":"string"},"card_type":{"type":"string","enum":["debit","credit"]},"linked_bank_account_id":{"type":"string","description":"Required for debit cards."},"credit_limit":{"type":"number"},"current_balance":{"type":"number"},"billing_day":{"type":"number","description":"Required for credit cards (1-31)."},"payment_due_day":{"type":"number","description":"Required for credit cards (1-31)."},"auto_pay":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/cards/:card_id","method":"GET","description":"Get a single card by ID.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/cards/:card_id","method":"PATCH","description":"Update a card.","inputSchema":{"type":"object","properties":{"label":{"type":"string"},"status":{"type":"string","enum":["active","closed"]},"current_balance":{"type":"number"},"credit_limit":{"type":"number"},"billing_day":{"type":"number"},"payment_due_day":{"type":"number"},"auto_pay":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/cards/:card_id","method":"DELETE","description":"Delete a card.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/debts","method":"GET","description":"List all debts (loans, mortgages, etc.).","outputSchema":{"type":"object","properties":{"debts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"lender":{"type":"string"},"loan_type":{"type":"string"},"payment_schedule":{"type":"string"},"initial_amount":{"type":"number"},"current_balance":{"type":"number"},"term_months":{"type":"number"},"payment_number":{"type":"integer","description":"Current payment number (payment X of Y)."},"total_payments":{"type":"integer","description":"Total number of payments (payment X of Y)."},"start_date":{"type":"string"},"created_at":{"type":"string"}}}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/debts","method":"POST","description":"Register a new debt from your latest loan invoice. Provide the remaining balance, interest rate, and payment number. Supports both full registration and invoice-based registration.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Debt name (e.g. 'Mortgage - Arion')."},"lender":{"type":"string"},"loan_type":{"type":"string","enum":["indexed","non_indexed"],"description":"Defaults to non_indexed."},"payment_schedule":{"type":"string","enum":["annuity","linear"],"description":"Defaults to annuity."},"initial_amount":{"type":"number","description":"Original loan amount in ISK."},"current_balance":{"type":"number","description":"Current outstanding balance."},"term_months":{"type":"number","description":"Loan term in months."},"start_date":{"type":"string","description":"Loan start date (YYYY-MM-DD)."},"asset_id":{"type":"string","description":"Optional linked asset ID."},"index_code":{"type":"string","description":"CPI index code for indexed loans."},"payment_number":{"type":"integer","description":"Current payment number from the latest invoice (payment X of Y)."},"total_payments":{"type":"integer","description":"Total number of payments from the latest invoice (payment X of Y)."},"interest_rate":{"type":"number","description":"Current annual interest rate as shown on the invoice. Creates an initial interest period automatically."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/debts/:debt_id","method":"GET","description":"Get a debt with its interest periods and upcoming payment schedule.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"lender":{"type":"string"},"loan_type":{"type":"string"},"payment_schedule":{"type":"string"},"initial_amount":{"type":"number"},"current_balance":{"type":"number"},"term_months":{"type":"number"},"payment_number":{"type":"integer","description":"Current payment number (payment X of Y)."},"total_payments":{"type":"integer","description":"Total number of payments (payment X of Y)."},"start_date":{"type":"string"},"interest_periods":{"type":"array","items":{"type":"object"}},"upcoming_payments":{"type":"array","items":{"type":"object"}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/debts/:debt_id","method":"PATCH","description":"Update a debt record.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"lender":{"type":"string"},"current_balance":{"type":"number"},"term_months":{"type":"number"},"loan_type":{"type":"string"},"payment_schedule":{"type":"string"},"index_code":{"type":"string"},"payment_number":{"type":"integer","description":"Current payment number (payment X of Y)."},"total_payments":{"type":"integer","description":"Total number of payments (payment X of Y)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/debts/:debt_id","method":"DELETE","description":"Delete a debt and its associated interest periods and payment schedule.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/assets","method":"GET","description":"List assets with current valuations. Filter by status (default: owned).","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["owned","disposed"]}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/assets","method":"POST","description":"Create a new asset (property, vehicle, investment, etc.).","inputSchema":{"type":"object","required":["name","asset_type","purchase_price","purchase_date"],"properties":{"name":{"type":"string"},"asset_type":{"type":"string","description":"e.g. 'real_estate', 'vehicle', 'investment'"},"purchase_price":{"type":"number"},"purchase_date":{"type":"string","description":"YYYY-MM-DD"},"currency":{"type":"string"},"financing_notes":{"type":"string"},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/assets/:asset_id","method":"GET","description":"Get an asset with valuation history and linked debts.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/assets/:asset_id","method":"PATCH","description":"Update an asset.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"asset_type":{"type":"string"},"status":{"type":"string","enum":["owned","disposed"]},"disposal_date":{"type":"string"},"disposal_amount":{"type":"number"},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/assets/:asset_id","method":"DELETE","description":"Delete an asset.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/summary","method":"GET","description":"Get a computed financial overview: bank balances, total debt, total assets, net worth, monthly income/costs, and net cash flow. All values in ISK.","outputSchema":{"type":"object","properties":{"bank_balance":{"type":"number"},"credit_owed":{"type":"number"},"total_liquid":{"type":"number"},"total_assets":{"type":"number"},"total_debt":{"type":"number"},"net_worth":{"type":"number"},"monthly_income":{"type":"number"},"monthly_costs":{"type":"number"},"net_monthly_cash_flow":{"type":"number"},"account_count":{"type":"number"},"card_count":{"type":"number"},"debt_count":{"type":"number"},"asset_count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/settings","method":"GET","description":"Get budgeting settings (currency, payment defaults, thresholds).","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/settings","method":"PATCH","description":"Update budgeting settings. Creates settings row if none exists.","inputSchema":{"type":"object","properties":{"currency":{"type":"string"},"default_payment_method_type":{"type":"string"},"default_payment_method_id":{"type":"string"},"minimum_balance_threshold":{"type":"number"},"large_outflow_lookahead_days":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/evidence","method":"GET","description":"List evidence attachments (receipts, statements, invoices) for a given transaction. Requires entity_type and entity_id. Returns file metadata with signed download URLs.","inputSchema":{"type":"object","required":["entity_type","entity_id"],"properties":{"entity_type":{"type":"string","enum":["revenue_transaction","fixed_cost_transaction","variable_cost_transaction","asset_transaction","credit_card_payment","debt_payment"],"description":"Transaction type to look up evidence for."},"entity_id":{"type":"string","description":"UUID of the transaction."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/evidence","method":"POST","description":"Attach evidence (receipt, statement, invoice) to a transaction. Provide entity_type, entity_id, file details, and an optional label.","inputSchema":{"type":"object","required":["entity_type","entity_id","file_name","file_path"],"properties":{"entity_type":{"type":"string","enum":["revenue_transaction","fixed_cost_transaction","variable_cost_transaction","asset_transaction","credit_card_payment","debt_payment"],"description":"Transaction type."},"entity_id":{"type":"string","description":"UUID of the transaction."},"file_name":{"type":"string","description":"Original file name."},"file_path":{"type":"string","description":"Storage path of the uploaded file."},"file_size":{"type":"number","description":"File size in bytes."},"mime_type":{"type":"string","description":"MIME type (e.g. image/jpeg, application/pdf)."},"label":{"type":"string","description":"Optional label (e.g. 'receipt', 'bank statement', 'invoice')."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/evidence/:evidence_id","method":"GET","description":"Get a single evidence attachment with a signed download URL.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/evidence/:evidence_id","method":"DELETE","description":"Delete an evidence attachment. Removes both the database record and the file from storage.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/monthly-budgets","method":"GET","description":"List monthly budgets with total spent per budget. Filter by year, status, or specific month (YYYY-MM).","inputSchema":{"type":"object","properties":{"year":{"type":"string","description":"Filter by year (YYYY)."},"month":{"type":"string","description":"Get a specific month (YYYY-MM)."},"status":{"type":"string","description":"Filter by status (draft, active, closed)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/monthly-budgets","method":"POST","description":"Create or get a monthly budget (idempotent). Uses ensure_monthly_budget internally. Optionally set available_amount and status.","inputSchema":{"type":"object","required":["budget_month"],"properties":{"budget_month":{"type":"string","description":"Month (YYYY-MM or YYYY-MM-DD)."},"available_amount":{"type":"number","description":"Budget amount available for the month."},"status":{"type":"string","description":"Budget status (draft, active, closed)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/monthly-budgets/:budget_id","method":"GET","description":"Get a monthly budget with allocations, spending breakdown by category, total spent, and transaction count.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/monthly-budgets/:budget_id","method":"PATCH","description":"Update a monthly budget (available_amount, status).","inputSchema":{"type":"object","properties":{"available_amount":{"type":"number"},"status":{"type":"string","description":"Budget status (draft, active, closed)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/merchants","method":"GET","description":"List merchants. Filter by name search, merchant_type, or include inactive.","inputSchema":{"type":"object","properties":{"search":{"type":"string","description":"Search merchant name (case-insensitive)."},"merchant_type":{"type":"string","description":"Filter by type (e.g. restaurant, grocery, liquor_store)."},"include_inactive":{"type":"boolean","description":"Include inactive merchants (default false)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/merchants","method":"POST","description":"Create a new merchant.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Merchant name."},"location":{"type":"string","description":"Location description."},"merchant_type":{"type":"string","description":"Type (e.g. restaurant, grocery, gas_station)."},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/merchants/:merchant_id","method":"GET","description":"Get a merchant with recent transactions.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/merchants/:merchant_id","method":"PATCH","description":"Update a merchant.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"location":{"type":"string"},"merchant_type":{"type":"string"},"notes":{"type":"string"},"is_active":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs","method":"GET","description":"List variable cost transactions. Filter by month (YYYY-MM), category_id, merchant_id, card_id, status. Defaults to all transactions, sorted by date descending.","inputSchema":{"type":"object","properties":{"month":{"type":"string","description":"Filter by month (YYYY-MM). Returns all if omitted."},"category_id":{"type":"string","description":"Filter by default category ID."},"merchant_id":{"type":"string","description":"Filter by merchant ID."},"card_id":{"type":"string","description":"Filter by card ID."},"status":{"type":"string","description":"Filter by status: suggestion, approved, rejected."},"limit":{"type":"number","description":"Max results (default 100)."},"offset":{"type":"number","description":"Pagination offset (default 0)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs","method":"POST","description":"Create a variable cost transaction. Auto-resolves monthly budget. Optionally include line_items array for multi-category receipts. Set status='suggestion' for AI-created transactions pending review.","inputSchema":{"type":"object","required":["transaction_date","total_amount","category_id"],"properties":{"transaction_date":{"type":"string","description":"Transaction date (YYYY-MM-DD)."},"total_amount":{"type":"number","description":"Total amount."},"category_id":{"type":"string","description":"Default category ID."},"vendor_description":{"type":"string","description":"Free-text vendor/merchant description."},"card_id":{"type":"string","description":"Payment card ID."},"merchant_id":{"type":"string","description":"Merchant ID."},"account_id":{"type":"string","description":"Accounting key ID for GL posting."},"status":{"type":"string","description":"Transaction status: suggestion (AI-created), approved (default), rejected."},"currency":{"type":"string","description":"Currency code (default ISK)."},"notes":{"type":"string","description":"Free-text notes."},"created_by":{"type":"string","description":"UUID of the creating user/agent. Null means owning user."},"payment_method_type":{"type":"string","description":"Payment method type: card or bank_account."},"payment_method_id":{"type":"string","description":"UUID of the card or bank account used for payment."},"line_items":{"type":"array","description":"Optional line items for multi-account transactions.","items":{"type":"object","required":["description","amount"],"properties":{"description":{"type":"string","description":"Line item description."},"amount":{"type":"number","description":"Line total amount."},"account_id":{"type":"string","description":"GL account ID from accounting_keys."},"quantity":{"type":"number","description":"Quantity (default 1)."},"unit_price":{"type":"number","description":"Price per unit."},"vat_rate":{"type":"number","description":"VAT rate as decimal (e.g., 0.11)."},"vat_amount":{"type":"number","description":"VAT amount included in line total."},"sort_order":{"type":"number","description":"Display order (default: array index)."},"notes":{"type":"string","description":"Line item notes."}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id","method":"GET","description":"Get a variable cost transaction with line items, evidence, merchant, card, and budget details.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id","method":"PATCH","description":"Update a variable cost transaction. When line_items array is provided, all existing line items are replaced (delete old, insert new).","inputSchema":{"type":"object","properties":{"transaction_date":{"type":"string"},"total_amount":{"type":"number"},"vendor_description":{"type":"string"},"category_id":{"type":"string","description":"Default category ID."},"card_id":{"type":"string"},"merchant_id":{"type":"string"},"account_id":{"type":"string"},"currency":{"type":"string","description":"Currency code."},"notes":{"type":"string","description":"Free-text notes."},"payment_method_type":{"type":"string","description":"Payment method type: card or bank_account."},"payment_method_id":{"type":"string","description":"UUID of the card or bank account."},"line_items":{"type":"array","description":"Replace all line items. Omit to keep existing line items unchanged.","items":{"type":"object","required":["description","amount"],"properties":{"description":{"type":"string"},"amount":{"type":"number"},"account_id":{"type":"string","description":"GL account ID from accounting_keys."},"quantity":{"type":"number"},"unit_price":{"type":"number"},"vat_rate":{"type":"number"},"vat_amount":{"type":"number"},"sort_order":{"type":"number"},"notes":{"type":"string"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id","method":"DELETE","description":"Delete a variable cost transaction. Cascades to line items and removes associated evidence files.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id/status","method":"PATCH","description":"Change the status of a variable cost transaction (approve or reject). Sets status_changed_at automatically. Use this for the AI suggestion review workflow.","inputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"New status: approved or rejected."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id/line-items","method":"GET","description":"List line items for a variable cost transaction with category labels.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id/line-items","method":"POST","description":"Add a line item to a variable cost transaction. Automatically sets has_line_items=true on the parent transaction.","inputSchema":{"type":"object","required":["description","amount"],"properties":{"description":{"type":"string","description":"Line item description."},"amount":{"type":"number","description":"Line total amount."},"account_id":{"type":"string","description":"GL account ID from accounting_keys."},"quantity":{"type":"number","description":"Quantity (default 1)."},"unit_price":{"type":"number","description":"Price per unit."},"vat_rate":{"type":"number","description":"VAT rate as decimal (e.g., 0.11)."},"vat_amount":{"type":"number","description":"VAT amount included in line total."},"sort_order":{"type":"number","description":"Display order (default 0)."},"notes":{"type":"string","description":"Line item notes."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id/line-items/:line_item_id","method":"DELETE","description":"Delete a line item from a variable cost transaction.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id/line-items/bulk","method":"PATCH","description":"Bulk update line item GL accounts for a transaction. Use during suggestion review to reassign accounts before approving. Validates that all line items belong to the transaction and all account IDs exist.","inputSchema":{"type":"object","required":["updates"],"properties":{"updates":{"type":"array","description":"Array of line item account updates.","items":{"type":"object","required":["line_item_id","account_id"],"properties":{"line_item_id":{"type":"string","description":"Line item UUID."},"account_id":{"type":"string","description":"GL account UUID from accounting_keys."}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/suggestions","method":"GET","description":"List variable cost transactions with status='suggestion' for review. Returns enriched data including merchant, card, category, monthly budget, line items with category labels, and evidence with signed URLs. Paginated with limit/offset.","inputSchema":{"type":"object","properties":{"limit":{"type":"integer","description":"Max results to return. Default 50."},"offset":{"type":"integer","description":"Offset for pagination. Default 0."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id/line-items/:line_item_id/ingredient","method":"PATCH","description":"Link or unlink a line item to/from an ingredient in public.ingredients. Setting ingredient_id to null clears the link. Used during transaction review to map grocery line items to ingredients for price tracking.","inputSchema":{"type":"object","properties":{"ingredient_id":{"type":["string","null"],"description":"Ingredient UUID, or null to clear the link."},"ingredient_quantity":{"type":"number","description":"Quantity of the ingredient in this line item."},"ingredient_unit":{"type":"string","description":"Unit of measure (l, kg, stk, etc.)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/variable-costs/:transaction_id/approve-with-ingredients","method":"POST","description":"Atomically approve a suggestion transaction while creating ingredient mappings and price observations. For each mapping: optionally creates a new ingredient, links the line item, and records a price observation (which auto-updates the ingredient's reference_price via trigger). Use when approving a grocery receipt and mapping its line items to ingredients.","inputSchema":{"type":"object","required":["ingredient_mappings"],"properties":{"ingredient_mappings":{"type":"array","items":{"type":"object","required":["line_item_id","quantity","unit"],"properties":{"line_item_id":{"type":"string","description":"Line item UUID."},"ingredient_id":{"type":"string","description":"Existing ingredient UUID (omit if using create_new)."},"quantity":{"type":"number","description":"Quantity purchased (e.g. 1 for 1L)."},"unit":{"type":"string","description":"Unit of measure (l, kg, stk, etc.)."},"create_new":{"type":"object","properties":{"name":{"type":"string","description":"New ingredient name."},"category":{"type":"string","description":"Category."},"default_unit":{"type":"string","description":"Default unit."}},"required":["name"],"description":"Create a new ingredient instead of referencing an existing one."}}},"description":"Array of line item → ingredient mappings."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/receipts/pending","method":"GET","description":"List receipts waiting in the upload inbox (pending_uploads where app='budgeting' and status='pending'). Use this to discover what receipts need to be extracted into suggestion transactions. Returns nested file metadata for each upload.","outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/receipts/:upload_id/extraction-context","method":"GET","description":"Get everything needed to extract line items from a pending receipt: signed URLs to view the receipt file(s), the candidate expense accounts (pre-filtered to type='expense' and not hidden), the user's default currency, the prompt_version string, and the instructions text. After extracting, post the parsed line items to /receipts/:upload_id/promote. This endpoint is read-only and does NOT mark the upload as processed.","outputSchema":{"type":"object","properties":{"upload":{"type":"object"},"files":{"type":"array","items":{"type":"object","properties":{"signed_url":{"type":"string","description":"Time-limited URL valid for 1 hour. Use this to view the receipt image."}}}},"accounts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":"string"},"name":{"type":"string"}}}},"default_currency":{"type":"string"},"prompt_version":{"type":"string"},"instructions":{"type":"string","description":"Instructions text describing what to extract and the JSON shape to post back."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/budgeting/receipts/:upload_id/promote","method":"POST","description":"Post the parsed line items extracted from a receipt back to the gateway. Atomically creates a variable_cost_transactions row in status='suggestion', the variable_cost_line_items rows with source='ai_initial', the line_item_ai_predictions snapshot rows linked back via ai_prediction_id, the transaction_evidence row pointing the receipt at the new transaction, and marks the pending_uploads row as completed. The user reviews and approves the suggestion in the budgeting app afterwards. model_version is REQUIRED so eval data is honest about which model produced each prediction.","inputSchema":{"type":"object","required":["model_version","line_items"],"properties":{"model_version":{"type":"string","description":"Exact identifier of the model that produced these line items (e.g. 'claude-sonnet-4-6'). Recorded on every prediction row."},"prompt_version":{"type":"string","description":"Optional prompt version override. Defaults to the PROMPT_VERSION returned by /extraction-context."},"vendor_name":{"type":"string","description":"Vendor/merchant name from the receipt."},"transaction_date":{"type":"string","description":"Transaction date (YYYY-MM-DD). Defaults to today if omitted."},"currency":{"type":"string","description":"ISO currency code (e.g. ISK, USD, EUR). Defaults to ISK."},"line_items":{"type":"array","description":"Array of extracted line items. Each item must include description, quantity, unit_price, discount, amount, account_id, and per-field confidence (high|medium|low).","items":{"type":"object","required":["description","quantity","unit_price","discount","amount","account_id","confidence"],"properties":{"description":{"type":"string"},"quantity":{"type":"number"},"unit_price":{"type":"number"},"discount":{"type":"number"},"amount":{"type":"number"},"account_id":{"type":"string","description":"Expense account UUID from /extraction-context.accounts."},"confidence":{"type":"object","required":["description","quantity","unit_price","discount","amount","account_id"],"properties":{"description":{"type":"string","enum":["high","medium","low"]},"quantity":{"type":"string","enum":["high","medium","low"]},"unit_price":{"type":"string","enum":["high","medium","low"]},"discount":{"type":"string","enum":["high","medium","low"]},"amount":{"type":"string","enum":["high","medium","low"]},"account_id":{"type":"string","enum":["high","medium","low"]}}}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"budgeting"},{"path":"/api/v1/budgeting/economic-indicators","method":"GET","description":"List economic indicators (CPI, inflation rates) for indexed loan calculations. Filter by type and date range.","inputSchema":{"type":"object","properties":{"type":{"type":"string","description":"Indicator type (e.g. 'CPI')."},"start_date":{"type":"string","description":"Filter from date (YYYY-MM-DD)."},"end_date":{"type":"string","description":"Filter to date (YYYY-MM-DD)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"budgeting"},{"path":"/api/v1/cpe/activities","method":"GET","description":"List CPE activities with optional filters. Returns activities with hours, provider, date, and status.","inputSchema":{"type":"object","properties":{"after":{"type":"string","description":"Filter activities on or after this date (YYYY-MM-DD)."},"before":{"type":"string","description":"Filter activities on or before this date (YYYY-MM-DD)."},"requirement_id":{"type":"string","description":"Filter by linked requirement."},"category_id":{"type":"string","description":"Filter by activity category."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/activities/create","method":"POST","description":"Log a new CPE activity. Specify title, date, hours, optional provider and notes. Link to categories and requirements.","inputSchema":{"type":"object","required":["title","date","hours"],"properties":{"title":{"type":"string","description":"Activity title."},"date":{"type":"string","description":"Activity date (YYYY-MM-DD)."},"hours":{"type":"number","description":"CPE hours earned (must be > 0)."},"provider":{"type":"string","description":"Training provider or organization."},"notes":{"type":"string","description":"Additional notes."},"proof_url":{"type":"string","description":"URL to proof/certificate."},"category_ids":{"type":"array","items":{"type":"string"},"description":"Category IDs to link."},"requirement_ids":{"type":"array","items":{"type":"string"},"description":"Requirement IDs to link."},"activity_status_id":{"type":"string","description":"Status ID (e.g., completed, registered)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"cpe"},{"path":"/api/v1/cpe/activities/:activity_id","method":"GET","description":"Get a CPE activity with its categories and linked requirements.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/activities/:activity_id","method":"PATCH","description":"Update a CPE activity.","inputSchema":{"type":"object","properties":{"title":{"type":"string"},"date":{"type":"string"},"hours":{"type":"number"},"provider":{"type":"string"},"notes":{"type":"string"},"proof_url":{"type":"string"},"activity_status_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"cpe"},{"path":"/api/v1/cpe/activities/:activity_id","method":"DELETE","description":"Delete a CPE activity and its category/requirement links.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"cpe"},{"path":"/api/v1/cpe/requirements","method":"GET","description":"List CPE requirements with progress (completed_hours, registered_hours vs required_hours). Filter by status.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["draft","active","completed"],"description":"Filter by requirement status."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/requirements/create","method":"POST","description":"Create a CPE requirement with certification, date range, required hours, and optional per-category components.","inputSchema":{"type":"object","required":["certification_id","date_from","date_to","required_hours"],"properties":{"certification_id":{"type":"string","description":"Certification this requirement is for."},"date_from":{"type":"string","description":"Period start (YYYY-MM-DD)."},"date_to":{"type":"string","description":"Period end (YYYY-MM-DD)."},"required_hours":{"type":"number","description":"Total hours required."},"status":{"type":"string","enum":["draft","active","completed"]},"components":{"type":"array","items":{"type":"object","required":["activity_category_id","required_hours"],"properties":{"activity_category_id":{"type":"string"},"required_hours":{"type":"number"}}},"description":"Per-category hour breakdown."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"cpe"},{"path":"/api/v1/cpe/requirements/:requirement_id","method":"GET","description":"Get a CPE requirement with components, progress breakdown by category, and aggregated hours.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/requirements/:requirement_id","method":"PATCH","description":"Update a CPE requirement.","inputSchema":{"type":"object","properties":{"date_from":{"type":"string"},"date_to":{"type":"string"},"required_hours":{"type":"number"},"status":{"type":"string","enum":["draft","active","completed"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"cpe"},{"path":"/api/v1/cpe/requirements/:requirement_id","method":"DELETE","description":"Delete a CPE requirement and its components.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"cpe"},{"path":"/api/v1/cpe/certifications","method":"GET","description":"List available CPE certifications (e.g., CPA, CISA).","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/categories","method":"GET","description":"List CPE activity categories (e.g., Ethics, Technical, Business).","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/dashboard","method":"GET","description":"Get CPE progress dashboard: active requirements with hours breakdown (completed/registered/required, % complete, per-category components), recent activities, upcoming deadlines, total hours this year.","outputSchema":{"type":"object","properties":{"progress":{"type":"array","description":"Active requirements with hours and component breakdown."},"recent_activities":{"type":"array","description":"Last 10 activities."},"upcoming_deadlines":{"type":"array","description":"Requirements ending within 90 days with days_remaining."},"total_hours_this_year":{"type":"number"},"default_requirement_id":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/settings","method":"GET","description":"Get CPE user preferences (default requirement).","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/cpe/settings","method":"PATCH","description":"Update CPE user preferences.","inputSchema":{"type":"object","properties":{"default_requirement_id":{"type":"string","description":"Set the default active requirement for dashboard."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"cpe"},{"path":"/api/v1/cpe/statuses","method":"GET","description":"List CPE activity statuses (e.g., Registered, Completed, Planned) with display order.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"cpe"},{"path":"/api/v1/wardrobe/items","method":"GET","description":"List wardrobe items with optional filters. Returns only published items by default (use status='draft' to see items awaiting metadata). Excludes archived items by default. Each item with an image includes image_path (relative storage path) and image_signed_url (time-limited URL valid for 1 hour).","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["draft","published"],"description":"Filter by item status. Defaults to published. Use 'draft' to see items awaiting metadata."},"category":{"type":"string","enum":["tops","bottoms","outerwear","shoes","accessories","activewear"],"description":"Filter by item category."},"subcategory":{"type":"string","description":"Filter by subcategory (e.g. polo, jeans, blazer)."},"occasion":{"type":"string","enum":["work","casual","golf","formal","date_night","travel"],"description":"Filter by occasion tag."},"season":{"type":"string","enum":["spring","summer","autumn","winter"],"description":"Filter by season tag."},"condition":{"type":"string","enum":["new","good","fair","worn","retired"],"description":"Filter by condition."},"palette_score":{"type":"number","description":"Filter by exact palette score (1-5)."},"search":{"type":"string","description":"Search by item name or brand (case-insensitive)."},"include_archived":{"type":"boolean","description":"Include archived items. Defaults to false."},"sort":{"type":"string","enum":["name","purchase_date","created_at"],"description":"Sort field. Defaults to created_at."},"order":{"type":"string","enum":["asc","desc"],"description":"Sort direction. Defaults to desc."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/items","method":"POST","description":"Create a new wardrobe item. Provide name and category at minimum. Optionally include image_base64 to upload a photo in the same call — the image will be uploaded to Supabase Storage and image_path will be populated automatically.","inputSchema":{"type":"object","required":["name","category"],"properties":{"name":{"type":"string","description":"Item name (e.g. 'Navy Lacoste Polo')."},"category":{"type":"string","enum":["tops","bottoms","outerwear","shoes","accessories","activewear"]},"subcategory":{"type":"string","description":"Specific type within category."},"brand":{"type":"string"},"size":{"type":"string"},"colors":{"type":"array","items":{"type":"string"},"description":"Array of color names."},"color_hex":{"type":"array","items":{"type":"string"},"description":"Optional hex codes matching colors array."},"palette_score":{"type":"number","description":"1-5 rating of how well item fits user's color season."},"purchase_price":{"type":"number"},"purchase_date":{"type":"string","description":"YYYY-MM-DD format."},"purchase_currency":{"type":"string","description":"Defaults to ISK."},"condition":{"type":"string","enum":["new","good","fair","worn","retired"],"description":"Defaults to good."},"seasons":{"type":"array","items":{"type":"string"}},"occasions":{"type":"array","items":{"type":"string"}},"image_path":{"type":"string","description":"Relative storage path in the wardrobe-images private bucket. Prefer image_base64 for new uploads."},"image_base64":{"type":"string","description":"Base64-encoded image data. When provided, the image is uploaded to storage and image_path is set automatically."},"content_type":{"type":"string","enum":["image/jpeg","image/png","image/webp"],"description":"MIME type of image_base64. Defaults to image/jpeg."},"notes":{"type":"string"},"status":{"type":"string","enum":["draft","published"],"description":"Item status. Defaults to published. Use 'draft' for image-only items awaiting metadata."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/items/:item_id","method":"GET","description":"Get a single wardrobe item by ID, including wear statistics (wear count, last worn, cost per wear).","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/items/:item_id","method":"PATCH","description":"Update a wardrobe item. Provide only the fields to change. Optionally include image_base64 to upload or replace a photo in the same call.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"category":{"type":"string","enum":["tops","bottoms","outerwear","shoes","accessories","activewear"]},"subcategory":{"type":"string"},"brand":{"type":"string"},"size":{"type":"string"},"colors":{"type":"array","items":{"type":"string"}},"color_hex":{"type":"array","items":{"type":"string"}},"palette_score":{"type":"number"},"purchase_price":{"type":"number"},"purchase_date":{"type":"string"},"purchase_currency":{"type":"string"},"condition":{"type":"string","enum":["new","good","fair","worn","retired"]},"seasons":{"type":"array","items":{"type":"string"}},"occasions":{"type":"array","items":{"type":"string"}},"image_path":{"type":"string"},"image_base64":{"type":"string","description":"Base64-encoded image data. When provided, the image is uploaded to storage and image_path is set automatically."},"content_type":{"type":"string","enum":["image/jpeg","image/png","image/webp"],"description":"MIME type of image_base64. Defaults to image/jpeg."},"notes":{"type":"string"},"is_archived":{"type":"boolean"},"status":{"type":"string","enum":["draft","published"],"description":"Item status. Use to promote drafts or revert to draft."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/items/:item_id","method":"DELETE","description":"Permanently delete a wardrobe item. Consider archiving (PATCH is_archived: true) instead.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/pending","method":"GET","description":"List pending wardrobe uploads awaiting cataloging. Returns uploads from the shared pending_uploads system filtered to wardrobe, with nested file metadata. Use this to find items awaiting review in the upload inbox.","outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/pending/:item_id/promote","method":"POST","description":"Promote a pending wardrobe upload to a published wardrobe item by providing full metadata. The upload_id is passed as :item_id. Requires name and category at minimum. Creates the wardrobe item, moves the uploaded image to its final storage path, and marks the upload as completed.","inputSchema":{"type":"object","required":["name","category"],"properties":{"name":{"type":"string","description":"Item name (e.g. 'Navy Lacoste Polo')."},"category":{"type":"string","enum":["tops","bottoms","outerwear","shoes","accessories","activewear"]},"subcategory":{"type":"string","description":"Specific type within category."},"brand":{"type":"string"},"size":{"type":"string"},"colors":{"type":"array","items":{"type":"string"},"description":"Array of color names."},"color_hex":{"type":"array","items":{"type":"string"},"description":"Optional hex codes matching colors array."},"palette_score":{"type":"number","description":"1-5 rating of how well item fits user's color season."},"purchase_price":{"type":"number"},"purchase_date":{"type":"string","description":"YYYY-MM-DD format."},"purchase_currency":{"type":"string","description":"Defaults to ISK."},"condition":{"type":"string","enum":["new","good","fair","worn","retired"],"description":"Defaults to good."},"seasons":{"type":"array","items":{"type":"string"}},"occasions":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/settings","method":"GET","description":"Get the user's wardrobe profile including their 16-season color analysis type.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"season_type":{"type":"string","description":"Color season ID, e.g. 'soft_autumn', 'true_winter'."},"season_notes":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/settings","method":"PATCH","description":"Create or update the user's wardrobe profile (season type and analysis notes). Creates if not exists.","inputSchema":{"type":"object","properties":{"season_type":{"type":"string","description":"16-season type ID: true_spring, light_spring, bright_spring, warm_spring, true_summer, light_summer, soft_summer, cool_summer, true_autumn, soft_autumn, dark_autumn, warm_autumn, true_winter, dark_winter, bright_winter, cool_winter."},"season_notes":{"type":"string","description":"Free-form notes about the user's color analysis."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/outfits","method":"GET","description":"List outfits with nested item details. Filter by occasion, season, or minimum rating.","inputSchema":{"type":"object","properties":{"occasion":{"type":"string","enum":["work","casual","golf","formal","date_night","travel"]},"season":{"type":"string","enum":["spring","summer","autumn","winter"]},"min_rating":{"type":"number","description":"Minimum rating (1-5)."},"sort":{"type":"string","enum":["name","created_at","rating"],"description":"Defaults to created_at."},"order":{"type":"string","enum":["asc","desc"],"description":"Defaults to desc."}}},"outputSchema":{"type":"object","properties":{"outfits":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/outfits","method":"POST","description":"Create an outfit with associated items. Provide item_ids to link wardrobe items to this outfit.","inputSchema":{"type":"object","required":["name","item_ids"],"properties":{"name":{"type":"string","description":"Outfit name."},"item_ids":{"type":"array","items":{"type":"string"},"description":"Array of wardrobe item UUIDs to include."},"occasions":{"type":"array","items":{"type":"string"}},"seasons":{"type":"array","items":{"type":"string"}},"rating":{"type":"number","description":"1-5 rating."},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/outfits/:outfit_id","method":"GET","description":"Get a single outfit with full item details and recent wear history.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/outfits/:outfit_id","method":"PATCH","description":"Update outfit metadata and optionally replace its items. If item_ids is provided, all existing items are replaced.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"occasions":{"type":"array","items":{"type":"string"}},"seasons":{"type":"array","items":{"type":"string"}},"rating":{"type":"number"},"notes":{"type":"string"},"item_ids":{"type":"array","items":{"type":"string"},"description":"If provided, replaces all outfit items."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/outfits/:outfit_id","method":"DELETE","description":"Delete an outfit and all its item associations.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/wear","method":"GET","description":"List wear log entries (newest first) with nested item and outfit details. Filter by month or occasion.","inputSchema":{"type":"object","properties":{"month":{"type":"string","description":"Filter by month (YYYY-MM format)."},"occasion":{"type":"string","enum":["work","casual","golf","formal","date_night","travel"]},"limit":{"type":"number","description":"Max entries to return. Defaults to 50."}}},"outputSchema":{"type":"object","properties":{"entries":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/wear","method":"POST","description":"Log what you wore. Provide item_ids for the individual items, and optionally outfit_id if it's a saved outfit. Use worn_date to backfill past entries.","inputSchema":{"type":"object","required":["item_ids"],"properties":{"item_ids":{"type":"array","items":{"type":"string"},"description":"Array of wardrobe item UUIDs that were worn."},"outfit_id":{"type":"string","description":"Optional outfit UUID if logging a saved outfit."},"worn_date":{"type":"string","description":"Date worn (YYYY-MM-DD). Defaults to today."},"occasion":{"type":"string"},"weather_note":{"type":"string"},"rating":{"type":"number","description":"1-5 how the outfit felt."},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/wear/:wear_id","method":"DELETE","description":"Delete a wear log entry and its item associations.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/wishlist","method":"GET","description":"List wishlist items. Excludes purchased items by default.","inputSchema":{"type":"object","properties":{"priority":{"type":"string","enum":["low","medium","high"],"description":"Filter by priority."},"category":{"type":"string","description":"Filter by category."},"show_purchased":{"type":"boolean","description":"Include purchased items. Defaults to false."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/wishlist","method":"POST","description":"Add an item to the wishlist.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"category":{"type":"string"},"brand":{"type":"string"},"estimated_price":{"type":"number"},"currency":{"type":"string","description":"Defaults to ISK."},"url":{"type":"string","description":"Product page URL."},"image_path":{"type":"string"},"palette_score":{"type":"number","description":"1-5 palette fit."},"priority":{"type":"string","enum":["low","medium","high"],"description":"Defaults to medium."},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/wishlist/:wishlist_id","method":"PATCH","description":"Update a wishlist item. Set is_purchased=true and purchased_item_id to mark as bought.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"category":{"type":"string"},"brand":{"type":"string"},"estimated_price":{"type":"number"},"currency":{"type":"string"},"url":{"type":"string"},"image_path":{"type":"string"},"palette_score":{"type":"number"},"priority":{"type":"string","enum":["low","medium","high"]},"notes":{"type":"string"},"is_purchased":{"type":"boolean"},"purchased_item_id":{"type":"string","description":"UUID of the wardrobe item created from this wishlist item."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/wishlist/:wishlist_id","method":"DELETE","description":"Delete a wishlist item.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing","method":"GET","description":"List all packing lists.","outputSchema":{"type":"object","properties":{"packing_lists":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing","method":"POST","description":"Create a new packing list for a trip.","inputSchema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Trip name."},"destination":{"type":"string"},"start_date":{"type":"string","description":"YYYY-MM-DD."},"end_date":{"type":"string","description":"YYYY-MM-DD."},"climate":{"type":"string","enum":["warm","cold","mixed"]},"activities":{"type":"array","items":{"type":"string"},"description":"Planned activities."},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing/:packing_list_id","method":"GET","description":"Get a packing list with all items and their packed status.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing/:packing_list_id","method":"PATCH","description":"Update packing list metadata.","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"destination":{"type":"string"},"start_date":{"type":"string"},"end_date":{"type":"string"},"climate":{"type":"string","enum":["warm","cold","mixed"]},"activities":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing/:packing_list_id","method":"DELETE","description":"Delete a packing list and all its item associations.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing/:packing_list_id/items","method":"POST","description":"Add items to a packing list.","inputSchema":{"type":"object","required":["item_ids"],"properties":{"item_ids":{"type":"array","items":{"type":"string"},"description":"Array of wardrobe item UUIDs to add."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing/:packing_list_id/items","method":"DELETE","description":"Remove items from a packing list.","inputSchema":{"type":"object","required":["item_ids"],"properties":{"item_ids":{"type":"array","items":{"type":"string"},"description":"Array of wardrobe item UUIDs to remove."}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/packing/:packing_list_id/items/:item_id","method":"PATCH","description":"Toggle packed status for an item in a packing list.","inputSchema":{"type":"object","required":["is_packed"],"properties":{"is_packed":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-sessions","method":"GET","description":"List color analysis sessions for the user. Returns sessions with drape counts, most recent first. Use to see session history and find sessions to review or continue.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["pending","in_progress","completed","needs_retake"],"description":"Filter by session status."},"session_type":{"type":"string","enum":["full","refinement","validation"],"description":"Filter by session type."}}},"outputSchema":{"type":"object","properties":{"sessions":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-sessions","method":"POST","description":"Create a new color analysis session. Defaults to status='pending' and confidence='provisional'. For refinement sessions, link to the previous session via previous_session_id.","inputSchema":{"type":"object","required":["session_type"],"properties":{"session_type":{"type":"string","enum":["full","refinement","validation"],"description":"Type of analysis session."},"previous_session_id":{"type":"string","description":"UUID of a previous session this one refines (for refinement type)."},"lighting_notes":{"type":"string","description":"Notes about lighting conditions during the session."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-sessions/:session_id","method":"GET","description":"Get a single color analysis session with all its drapes joined (ordered by sort_order). This is the primary tool for reviewing a full session — returns the session metadata plus every drape assessment.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"session_type":{"type":"string"},"status":{"type":"string"},"undertone":{"type":["string","null"]},"value_level":{"type":["string","null"]},"chroma":{"type":["string","null"]},"season_result":{"type":["string","null"]},"confidence":{"type":"string"},"drapes":{"type":"array","items":{"type":"object"}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-sessions/:session_id","method":"PATCH","description":"Update a color analysis session. Use to record the three-axis determination (undertone, value_level, chroma), season_result, confidence, and status after analyzing drapes. Auto-sets updated_at.","inputSchema":{"type":"object","properties":{"undertone":{"type":["string","null"],"enum":["warm","cool","neutral_warm","neutral_cool","neutral",null],"description":"Hue axis result."},"value_level":{"type":["string","null"],"enum":["light","medium","deep",null],"description":"Depth axis result."},"chroma":{"type":["string","null"],"enum":["soft","clear",null],"description":"Intensity axis result."},"season_result":{"type":["string","null"],"description":"16-season type (e.g. 'soft_autumn', 'true_winter')."},"confidence":{"type":"string","enum":["provisional","moderate","high"],"description":"Confidence in the determination."},"status":{"type":"string","enum":["pending","in_progress","completed","needs_retake"],"description":"Session status."},"notes":{"type":["string","null"],"description":"Analysis notes."},"lighting_notes":{"type":["string","null"],"description":"Lighting condition notes."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-drapes","method":"POST","description":"Record a single drape assessment within a color analysis session. A drape represents one color fabric held against the subject, with AI-assessed reactions. Required: session_id, drape_category, color_name. Assessment fields (score, skin_reaction, eye_effect, etc.) can be set now or updated later.","inputSchema":{"type":"object","required":["session_id","drape_category","color_name"],"properties":{"session_id":{"type":"string","description":"UUID of the session this drape belongs to."},"drape_category":{"type":"string","enum":["undertone","value","chroma","season_confirmation","individual_color"],"description":"Which diagnostic phase this drape belongs to."},"color_name":{"type":"string","description":"Name of the drape color (e.g. 'Gold', 'Silver', 'Dusty Rose')."},"color_hex":{"type":"string","description":"Hex code of the drape color."},"color_temperature":{"type":"string","enum":["warm","cool","neutral"],"description":"Temperature classification of the color."},"image_path":{"type":"string","description":"Storage path of the draping photo."},"score":{"type":"number","description":"AI assessment score 1-10. 8-10=best, 6-7=good, 4-5=neutral, 1-3=avoid."},"skin_reaction":{"type":"string","description":"How the skin responded (e.g. 'glowing', 'sallow', 'even')."},"eye_effect":{"type":"string","description":"Effect on the eyes (e.g. 'brightened', 'sparkled', 'dulled')."},"contrast_effect":{"type":"string","description":"Effect on facial contrast."},"overall_harmony":{"type":"string","enum":["excellent","good","fair","poor","clashing"],"description":"Overall harmony rating."},"analysis_notes":{"type":"string","description":"Detailed AI analysis of this drape."},"needs_retake":{"type":"boolean","description":"Whether this drape photo needs to be retaken."},"retake_reason":{"type":"string","description":"Why a retake is needed."},"sort_order":{"type":"number","description":"Display order within the session."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-drapes/batch","method":"POST","description":"Record multiple drape assessments at once. All drapes must belong to the same session. Use when recording a full diagnostic phase (e.g. all undertone drapes). Maximum 50 drapes per batch.","inputSchema":{"type":"object","required":["drapes"],"properties":{"drapes":{"type":"array","items":{"type":"object","required":["session_id","drape_category","color_name"],"properties":{"session_id":{"type":"string"},"drape_category":{"type":"string","enum":["undertone","value","chroma","season_confirmation","individual_color"]},"color_name":{"type":"string"},"color_hex":{"type":"string"},"color_temperature":{"type":"string","enum":["warm","cool","neutral"]},"image_path":{"type":"string"},"score":{"type":"number"},"skin_reaction":{"type":"string"},"eye_effect":{"type":"string"},"contrast_effect":{"type":"string"},"overall_harmony":{"type":"string","enum":["excellent","good","fair","poor","clashing"]},"analysis_notes":{"type":"string"},"needs_retake":{"type":"boolean"},"retake_reason":{"type":"string"},"sort_order":{"type":"number"}}},"description":"Array of drape objects. All must have the same session_id."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-drapes/:drape_id","method":"PATCH","description":"Update an existing drape assessment. Use to add or revise AI assessment fields (score, skin_reaction, eye_effect, etc.) after initial creation.","inputSchema":{"type":"object","properties":{"drape_category":{"type":"string","enum":["undertone","value","chroma","season_confirmation","individual_color"]},"color_name":{"type":"string"},"color_hex":{"type":["string","null"]},"color_temperature":{"type":["string","null"],"enum":["warm","cool","neutral",null]},"image_path":{"type":["string","null"]},"score":{"type":["number","null"],"description":"AI assessment score 1-10."},"skin_reaction":{"type":["string","null"]},"eye_effect":{"type":["string","null"]},"contrast_effect":{"type":["string","null"]},"overall_harmony":{"type":["string","null"],"enum":["excellent","good","fair","poor","clashing",null]},"analysis_notes":{"type":["string","null"]},"needs_retake":{"type":"boolean"},"retake_reason":{"type":["string","null"]},"sort_order":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-sessions/:session_id/photos","method":"GET","description":"Retrieve base64-encoded drape photos for a session. Returns all drapes that have an image_path, with the photo data inline as base64. This is the KEY tool for analyzing draping photos in conversation — it lets Claude see the actual images.","outputSchema":{"type":"object","properties":{"photos":{"type":"array","items":{"type":"object","properties":{"drape_id":{"type":"string"},"color_name":{"type":"string"},"drape_category":{"type":"string"},"sort_order":{"type":"number"},"image_base64":{"type":["string","null"]},"content_type":{"type":["string","null"]}}}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-palette","method":"GET","description":"List the user's personal color palette. Colors are sorted by rating priority (best first). Use to see which colors suit the user based on draping analysis.","inputSchema":{"type":"object","properties":{"rating":{"type":"string","enum":["best","good","neutral","avoid"],"description":"Filter by rating tier."},"color_family":{"type":"string","description":"Filter by color family (e.g. earth_tones, jewel_tones, pastels, neutrals, brights, metals)."}}},"outputSchema":{"type":"object","properties":{"colors":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-palette","method":"POST","description":"Add or update a color in the personal palette. Upserts on (user_id, color_hex) — if the hex already exists, updates the existing entry. Use after draping analysis to build the user's palette.","inputSchema":{"type":"object","required":["color_name","color_hex","rating"],"properties":{"color_name":{"type":"string","description":"Name of the color (e.g. 'Dusty Rose', 'Emerald Green')."},"color_hex":{"type":"string","description":"Hex code (e.g. '#B76E79'). Stored lowercase."},"rating":{"type":"string","enum":["best","good","neutral","avoid"],"description":"How well this color suits the user."},"color_family":{"type":"string","description":"Color family (e.g. earth_tones, jewel_tones, pastels, neutrals, brights, metals)."},"source_drape_id":{"type":"string","description":"UUID of the drape that identified this color."},"source_session_id":{"type":"string","description":"UUID of the session this color came from."},"notes":{"type":"string","description":"Notes about why this color works/doesn't work."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-palette/:color_id","method":"DELETE","description":"Remove a color from the personal palette.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/color-sessions/:session_id/apply","method":"POST","description":"Apply a completed session's color analysis results to the user's wardrobe profile. Validates the session is completed and has a season_result. Updates wardrobe.profiles with season_type, undertone, value_level, chroma, and confidence. Sets superseded_by on the previously active session. Only use after a full analysis is done and confirmed.","outputSchema":{"type":"object","properties":{"profile":{"type":"object"},"applied_session_id":{"type":"string"},"superseded_session_id":{"type":["string","null"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/items/:item_id/image","method":"POST","description":"Upload or replace a wardrobe item photo. Accepts EITHER a publicly accessible image_url (preferred — avoids base64 size limits) OR base64-encoded image data. The image is stored in a private Supabase Storage bucket (wardrobe-images). The response includes storage_path (relative path within the bucket) and image_signed_url (a time-limited URL valid for 1 hour). Use image_signed_url for display — do not cache it beyond expiry.","inputSchema":{"type":"object","required":[],"properties":{"image_url":{"type":"string","description":"Publicly accessible URL of the image to upload. The server fetches it directly — avoids base64 size limits. Preferred over the image parameter. Supports JPEG, PNG, and WebP (max 10 MB)."},"image":{"type":"string","description":"Base64-encoded image data. May include a data URI prefix (e.g. 'data:image/jpeg;base64,...') — the prefix will be stripped automatically. Max 5 MB decoded. Use image_url instead when possible."},"content_type":{"type":"string","description":"MIME type of the image. Only needed with base64 image parameter. Supported values: 'image/jpeg', 'image/png', 'image/webp'. Defaults to 'image/jpeg'. Ignored when using image_url (detected automatically)."}}},"outputSchema":{"type":"object","properties":{"storage_path":{"type":"string","description":"Relative path within the wardrobe-images private bucket (e.g. {user_id}/{item_id}.jpg)."},"image_signed_url":{"type":["string","null"],"description":"Time-limited signed URL for displaying the image (valid for 1 hour). Do not cache beyond expiry."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/wardrobe/items/:item_id/image","method":"DELETE","description":"Remove a wardrobe item's photo from the private wardrobe-images bucket and clear the image_path.","inputSchema":{"type":"object","properties":{}},"outputSchema":{"type":"object","properties":{"message":{"type":"string","description":"Confirmation message."}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"wardrobe"},{"path":"/api/v1/calendar/events","method":"GET","description":"List calendar events for a date range. Returns both personal and family events. Recurring events are expanded into individual occurrences within the range by default. Use expand_recurring=false to get raw recurring event records instead.","inputSchema":{"type":"object","required":["start_date","end_date"],"properties":{"start_date":{"type":"string","description":"Range start date (YYYY-MM-DD)."},"end_date":{"type":"string","description":"Range end date (YYYY-MM-DD)."},"category":{"type":"string","enum":["school","sports","medical","social","travel","work","other"],"description":"Filter by event category."},"family_only":{"type":"boolean","description":"If true, only return family events (exclude personal)."},"search":{"type":"string","description":"Search in event title and description (case-insensitive)."},"expand_recurring":{"type":"boolean","description":"Expand recurring events into individual occurrences. Defaults to true."}}},"outputSchema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"calendar"},{"path":"/api/v1/calendar/today","method":"GET","description":"Get today's calendar events across all calendars, sorted by start time. Recurring events are automatically expanded. Useful for daily briefings and 'what's on today' queries.","outputSchema":{"type":"object","properties":{"events":{"type":"array","items":{"type":"object"}},"count":{"type":"number"},"date":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"calendar"},{"path":"/api/v1/calendar/events/:event_id","method":"GET","description":"Get a single calendar event by ID with full details including recurrence exceptions.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"calendar"},{"path":"/api/v1/calendar/events","method":"POST","description":"Create a new calendar event. Set family_id for family events, or omit for personal events. Set is_recurring=true with recurrence_frequency to create recurring events.","inputSchema":{"type":"object","required":["title","start_date"],"properties":{"title":{"type":"string","description":"Event title."},"description":{"type":"string"},"location":{"type":"string"},"start_date":{"type":"string","description":"Start date (YYYY-MM-DD)."},"start_time":{"type":"string","description":"Start time (HH:MM). Omit for all-day events."},"end_date":{"type":"string","description":"End date (YYYY-MM-DD). Omit if same day."},"end_time":{"type":"string","description":"End time (HH:MM)."},"all_day":{"type":"boolean","description":"All-day event. Defaults to false."},"is_recurring":{"type":"boolean","description":"Whether event recurs. Defaults to false."},"recurrence_frequency":{"type":"string","enum":["daily","weekly","monthly","yearly"],"description":"Required if is_recurring=true."},"recurrence_interval":{"type":"number","description":"Repeat every N units. Defaults to 1."},"recurrence_end_date":{"type":"string","description":"When recurrence stops (YYYY-MM-DD)."},"recurrence_days_of_week":{"type":"array","items":{"type":"number"},"description":"For weekly: days (0=Sun, 1=Mon, ..., 6=Sat)."},"category":{"type":"string","enum":["school","sports","medical","social","travel","work","other"]},"color":{"type":"string","description":"Hex color override."},"family_id":{"type":"string","description":"Set to create a family event. Omit for personal."},"assigned_to":{"type":"string","description":"Family member UUID this event is for."},"reminder_minutes":{"type":"number","description":"Minutes before event to remind."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"calendar"},{"path":"/api/v1/calendar/events/:event_id","method":"PATCH","description":"Update a calendar event. Provide only the fields to change.","inputSchema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"location":{"type":"string"},"start_date":{"type":"string"},"start_time":{"type":"string"},"end_date":{"type":"string"},"end_time":{"type":"string"},"all_day":{"type":"boolean"},"is_recurring":{"type":"boolean"},"recurrence_frequency":{"type":"string","enum":["daily","weekly","monthly","yearly"]},"recurrence_interval":{"type":"number"},"recurrence_end_date":{"type":"string"},"recurrence_days_of_week":{"type":"array","items":{"type":"number"}},"category":{"type":"string","enum":["school","sports","medical","social","travel","work","other"]},"color":{"type":"string"},"assigned_to":{"type":"string"},"reminder_minutes":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"calendar"},{"path":"/api/v1/calendar/events/:event_id","method":"DELETE","description":"Delete a calendar event. For recurring events, this deletes the entire series. Use recurrence exceptions to cancel single occurrences instead.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"calendar"},{"path":"/api/v1/calendar/events/:event_id/exceptions","method":"POST","description":"Create a recurrence exception to cancel or modify a single occurrence of a recurring event. Set is_cancelled=true to skip the occurrence, or provide override fields (title, start_time, etc.) to modify it.","inputSchema":{"type":"object","required":["original_date"],"properties":{"original_date":{"type":"string","description":"The date of the occurrence to modify (YYYY-MM-DD)."},"is_cancelled":{"type":"boolean","description":"If true, this occurrence is cancelled/hidden."},"title":{"type":"string","description":"Override title for this occurrence."},"description":{"type":"string"},"start_time":{"type":"string","description":"Override start time (HH:MM)."},"end_time":{"type":"string","description":"Override end time (HH:MM)."},"location":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"calendar"},{"path":"/api/v1/notifications/send","method":"POST","description":"Send a notification. Creates a notification record, evaluates user preferences (quiet hours, channel config, type overrides), and delivers to enabled channels. In-app delivery is always included.","inputSchema":{"type":"object","required":["source_app","notification_type","priority","title"],"properties":{"user_id":{"type":"string","description":"Target user UUID. If omitted, sends to the authenticated user."},"source_app":{"type":"string","description":"App sending the notification: tasks, fitness, meals, agent, runner, hub, cpe, notes, budgeting."},"notification_type":{"type":"string","description":"Notification type: task_due, workout_reminder, chain_complete, chain_failed, morning_briefing, digest_published, security_alert, etc."},"priority":{"type":"string","enum":["urgent","normal","low"],"description":"Delivery priority. Urgent bypasses quiet hours. Low is in-app only."},"title":{"type":"string","description":"Notification title."},"body":{"type":"string","description":"Optional body text (markdown supported)."},"action_url":{"type":"string","description":"Deep link URL to the relevant resource."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notifications"},{"path":"/api/v1/notifications","method":"GET","description":"List notifications for the authenticated user, newest first. Use unread_only=true for the notification bell dropdown.","inputSchema":{"type":"object","properties":{"unread_only":{"type":"boolean","description":"If true, return only unread notifications."},"source_app":{"type":"string","description":"Filter by source app."},"limit":{"type":"number","description":"Max results (default 20, max 100)."},"offset":{"type":"number","description":"Pagination offset (default 0)."}}},"outputSchema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"total":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"notifications"},{"path":"/api/v1/notifications/unread-count","method":"GET","description":"Get the count of unread notifications. Called frequently by the notification bell component.","outputSchema":{"type":"object","properties":{"total":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"notifications"},{"path":"/api/v1/notifications/:notification_id/read","method":"PATCH","description":"Mark a single notification as read.","annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notifications"},{"path":"/api/v1/notifications/read-all","method":"PATCH","description":"Mark all unread notifications as read. Optionally filter by source_app.","inputSchema":{"type":"object","properties":{"source_app":{"type":"string","description":"Only mark notifications from this app as read."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notifications"},{"path":"/api/v1/notifications/preferences","method":"GET","description":"Get the user's notification preferences (quiet hours, channel config, type overrides). Returns defaults if no preferences are set.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"notifications"},{"path":"/api/v1/notifications/preferences","method":"PATCH","description":"Update notification preferences. Provide only fields to change. Creates a preferences row if none exists.","inputSchema":{"type":"object","properties":{"quiet_hours_start":{"type":"string","description":"Quiet hours start time (HH:MM)."},"quiet_hours_end":{"type":"string","description":"Quiet hours end time (HH:MM)."},"quiet_hours_tz":{"type":"string","description":"IANA timezone for quiet hours."},"global_mute":{"type":"boolean","description":"Mute all notifications."},"channel_config":{"type":"object","description":"Per-channel settings: { pushover: { enabled, user_key, api_token }, ntfy: { enabled, server_url, topic }, resend: { enabled, email }, in_app: { enabled } }"},"type_overrides":{"type":"object","description":"Per-type overrides: { notification_type: { channels: [...], priority_override: '...' } }"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"notifications"},{"path":"/api/v1/agents","method":"GET","description":"List all agents in the agent registry. Returns agents with their specializations and tool access rules. Use to see what agents are available, filter by active status or specialization domain.","inputSchema":{"type":"object","properties":{"is_active":{"type":"string","enum":["true","false"],"description":"Filter by active status. Omit to return all agents."},"specialization":{"type":"string","description":"Filter by specialization domain (e.g. 'database', 'frontend', 'api', 'infrastructure', 'qa', 'documentation')."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/:slug","method":"GET","description":"Get full details for a single agent by slug. Returns the agent record with specializations, tool access rules, file restrictions, and latest performance metrics.","inputSchema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","description":"Agent slug identifier (e.g. 'db-architect', 'frontend-dev')."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/create","method":"POST","description":"Create a new agent in the registry with specializations, tool access rules, and file restrictions. Each agent defines a specialized Claude Code subagent with a specific model tier, system prompt, and capability constraints.","inputSchema":{"type":"object","required":["name","slug","model"],"properties":{"name":{"type":"string","description":"Display name (e.g. 'DB Architect')."},"slug":{"type":"string","description":"URL-safe identifier used in chain assignments. Must be lowercase letters, numbers, and hyphens (e.g. 'db-architect')."},"description":{"type":"string","description":"When this agent should be invoked."},"model":{"type":"string","enum":["opus","sonnet","haiku"],"description":"Claude model tier for cost routing."},"system_prompt_template":{"type":"string","description":"Full system prompt sent to the Claude Code subagent."},"is_active":{"type":"boolean","description":"Whether the agent is active. Defaults to true."},"color":{"type":"string","description":"Hex color for UI display (e.g. '#4A90D9')."},"icon":{"type":"string","description":"Emoji or icon identifier."},"specializations":{"type":"array","items":{"type":"object","required":["specialization"],"properties":{"specialization":{"type":"string","description":"Domain tag (e.g. 'database', 'frontend', 'api')."},"proficiency":{"type":"string","enum":["primary","secondary"],"description":"Proficiency level. Defaults to 'primary'."}}},"description":"Capability tags for matching the agent to work."},"tool_access":{"type":"array","items":{"type":"object","required":["tool_name"],"properties":{"tool_name":{"type":"string","description":"Claude Code tool name."},"access_level":{"type":"string","enum":["allowed","denied"],"description":"Whether the tool is allowed or denied. Defaults to 'allowed'."}}},"description":"Tool access rules for the agent."},"file_restrictions":{"type":"array","items":{"type":"object","required":["path_pattern","restriction_type"],"properties":{"path_pattern":{"type":"string","description":"Glob pattern (e.g. '/supabase/**', '/app/**')."},"restriction_type":{"type":"string","enum":["allow","deny"],"description":"Whether the path is allowed or denied."}}},"description":"File path access restrictions."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/:slug","method":"PATCH","description":"Update an existing agent. Any subset of fields can be provided. If specializations[], tool_access[], or file_restrictions[] are provided, they replace all existing entries for that relation.","inputSchema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","description":"Path parameter: agent slug to update."},"name":{"type":"string","description":"New display name."},"description":{"type":["string","null"],"description":"New description or null to clear."},"model":{"type":"string","enum":["opus","sonnet","haiku"],"description":"New model tier."},"system_prompt_template":{"type":["string","null"],"description":"New system prompt or null to clear."},"is_active":{"type":"boolean","description":"Set active status."},"color":{"type":"string","description":"New hex color."},"icon":{"type":"string","description":"New icon."},"specializations":{"type":"array","items":{"type":"object","required":["specialization"],"properties":{"specialization":{"type":"string"},"proficiency":{"type":"string","enum":["primary","secondary"]}}},"description":"Replaces all specializations."},"tool_access":{"type":"array","items":{"type":"object","required":["tool_name"],"properties":{"tool_name":{"type":"string"},"access_level":{"type":"string","enum":["allowed","denied"]}}},"description":"Replaces all tool access rules."},"file_restrictions":{"type":"array","items":{"type":"object","required":["path_pattern","restriction_type"],"properties":{"path_pattern":{"type":"string"},"restriction_type":{"type":"string","enum":["allow","deny"]}}},"description":"Replaces all file restrictions."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/:slug","method":"DELETE","description":"Deactivate an agent (soft delete — sets is_active to false). The agent and its data are preserved but it will no longer appear in active agent lists.","inputSchema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","description":"Path parameter: agent slug to deactivate."}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/analytics","method":"GET","description":"Get aggregated analytics summary across all chains. Filter by period (week/month/all) and optionally by agent_slug. Returns total tasks, tokens, cost, duration, success rate, and per-agent breakdown.","inputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["week","month","all"],"description":"Time period to aggregate over. Defaults to 'all'."},"agent_slug":{"type":"string","description":"Filter breakdown to a specific agent slug."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/analytics/:chainId","method":"GET","description":"Get stored analytics for a specific chain. Returns the last computed analytics snapshot. Run agents_analytics_compute first if no data exists.","inputSchema":{"type":"object","required":["chainId"],"properties":{"chainId":{"type":"string","description":"Path parameter: chain ID."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/analytics/:chainId","method":"POST","description":"Store computed analytics for a chain. Called by agents_analytics_compute after computing metrics. Upserts on chain_id.","inputSchema":{"type":"object","required":["chainId"],"properties":{"chainId":{"type":"string","description":"Path parameter: chain ID."},"project_id":{"type":"string"},"project_name":{"type":"string"},"chain_type":{"type":"string"},"total_tasks":{"type":"number"},"completed_tasks":{"type":"number"},"failed_tasks":{"type":"number"},"total_tokens":{"type":"number"},"total_cost_estimate":{"type":"number"},"total_duration_ms":{"type":"number"},"avg_tokens_per_task":{"type":"number"},"avg_duration_per_task_ms":{"type":"number"},"agent_breakdown":{"type":"object"},"qa_summary":{"type":"object"},"failure_points":{"type":"array","items":{"type":"object"}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/metrics/:slug","method":"GET","description":"Get performance metrics history for a specific agent by slug. Returns the last 10 metric periods.","inputSchema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","description":"Path parameter: agent slug."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/qa/run","method":"POST","description":"Start a QA run for a task. Creates a qa_run record in 'running' status. Call qa_run_complete (PATCH) when the stage finishes.","inputSchema":{"type":"object","required":["task_id","stage"],"properties":{"task_id":{"type":"string","description":"The task ID being reviewed."},"chain_id":{"type":"string","description":"Optional chain ID this task belongs to."},"stage":{"type":"string","enum":["automated","code_review","integration","human_review"],"description":"QA pipeline stage."},"agent_slug":{"type":"string","description":"Slug of the agent running this stage (e.g. 'qa-reviewer')."},"model_used":{"type":"string","description":"Claude model used (e.g. 'haiku', 'sonnet')."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/qa/run/:id","method":"PATCH","description":"Complete a QA run with results. Updates the run status and creates any findings in one operation. Returns the completed run with its findings.","inputSchema":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","description":"Path parameter: QA run ID to complete."},"status":{"type":"string","enum":["passed","failed","warning","skipped"],"description":"Outcome of the QA stage."},"summary":{"type":"string","description":"Brief human-readable summary of results."},"completed_at":{"type":"string","description":"ISO timestamp when the run completed. Defaults to now."},"tokens_used":{"type":"number","description":"Total tokens consumed by this QA stage."},"duration_ms":{"type":"number","description":"Wall-clock duration in milliseconds."},"findings":{"type":"array","items":{"type":"object","required":["severity","title"],"properties":{"severity":{"type":"string","enum":["info","warning","error","critical"],"description":"Finding severity level."},"category":{"type":"string","description":"Category: verification, build, test, lint, security, convention, architecture, integration."},"title":{"type":"string","description":"Short description of the finding."},"description":{"type":"string","description":"Detailed explanation."},"file_path":{"type":"string","description":"Relevant file path, if applicable."},"line_number":{"type":"number","description":"Relevant line number, if applicable."},"suggestion":{"type":"string","description":"How to fix the issue."},"auto_fixable":{"type":"boolean","description":"Whether the QA agent can fix this automatically."}}},"description":"Findings discovered during this QA stage."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/qa/task/:taskId","method":"GET","description":"Get all QA runs and their findings for a task. Returns runs ordered by stage with nested findings. Use to see the full QA picture for a completed task.","inputSchema":{"type":"object","required":["taskId"],"properties":{"taskId":{"type":"string","description":"Path parameter: task ID to look up QA results for."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/qa/config","method":"GET","description":"Resolve the effective qa_pipeline_config JSONB for a task by walking task_id → chain_task → repository. Returns the repository's config (with stage keys like 'automated', 'code_review', 'integration'), plus repo metadata (slug, stack_tags, package_manager, runner_clone_path) so QA tools can choose the right commands for the repo's stack. For non-chain tasks or when the config is empty, returns an empty object — callers should fall back to hardcoded defaults.","inputSchema":{"type":"object","required":["task_id"],"properties":{"task_id":{"type":"string","description":"Task UUID."}}},"outputSchema":{"type":"object","properties":{"task_id":{"type":"string"},"is_chain_task":{"type":"boolean"},"repository":{"type":["object","null"],"properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"primary_language":{"type":["string","null"]},"stack_tags":{"type":"array","items":{"type":"string"}},"package_manager":{"type":["string","null"]},"runner_clone_path":{"type":["string","null"]}}},"qa_pipeline_config":{"type":"object","description":"Per-stage config keyed by stage name (automated, code_review, integration). Empty object means use defaults."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/qa/findings","method":"GET","description":"Query QA findings across tasks with optional filters. Returns findings enriched with their parent run context. Supports optional repository_id filter to scope findings to tasks targeting a specific repository. Useful for dashboards and trend analysis.","inputSchema":{"type":"object","properties":{"severity":{"type":"string","enum":["info","warning","error","critical"],"description":"Filter by finding severity."},"category":{"type":"string","description":"Filter by category (e.g. 'build', 'test', 'lint', 'security')."},"status":{"type":"string","enum":["running","passed","failed","warning","skipped"],"description":"Filter by parent run status."},"chain_id":{"type":"string","description":"Filter by chain ID."},"repository_id":{"type":"string","description":"Filter to findings on tasks targeting a specific repository (dev.repositories.id)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/patterns","method":"GET","description":"List all patterns for the user with optional filters. Returns patterns ordered by usage_count descending. Patterns are reusable templates for recurring task scenarios. Supports optional repository_id and organization_id filters: repository_id returns both repo-scoped and parent-org-scoped patterns; organization_id returns only org-scoped patterns.","inputSchema":{"type":"object","properties":{"category":{"type":"string","description":"Filter by pattern category (e.g. 'database', 'api', 'frontend')."},"is_active":{"type":"string","enum":["true","false"],"description":"Filter by active status. Omit to return all."},"repository_id":{"type":"string","description":"Filter to patterns scoped to this repo OR to its parent organization (patterns without a repo binding)."},"organization_id":{"type":"string","description":"Filter to patterns scoped to this organization."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/patterns","method":"POST","description":"Create a new pattern. Patterns capture reusable templates for recurring task scenarios with usage tracking.","inputSchema":{"type":"object","required":["name","slug"],"properties":{"name":{"type":"string","description":"Human-readable pattern name."},"slug":{"type":"string","description":"URL-safe identifier matching [a-z][a-z0-9-]*. Must be unique per user."},"category":{"type":"string","description":"Pattern category for grouping (e.g. 'database', 'api', 'testing')."},"trigger_description":{"type":"string","description":"Description of when to apply this pattern. Used for relevance matching."},"template_content":{"type":"string","description":"The pattern template, instructions, or checklist content."},"source_chain_ids":{"type":"array","items":{"type":"string"},"description":"Chain IDs from which this pattern was extracted."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/patterns/:slug","method":"GET","description":"Get full details for a single pattern including its 20 most recent usage records.","inputSchema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","description":"Pattern slug identifier."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/patterns/:slug","method":"PATCH","description":"Update a pattern's fields. Only provided fields are updated. Supports updating name, category, trigger_description, template_content, is_active, and source_chain_ids.","inputSchema":{"type":"object","required":["slug"],"properties":{"slug":{"type":"string","description":"Pattern slug to update."},"name":{"type":"string","description":"New pattern name."},"category":{"type":"string","description":"New category."},"trigger_description":{"type":"string","description":"New trigger description."},"template_content":{"type":"string","description":"New template content."},"is_active":{"type":"boolean","description":"Enable or disable the pattern."},"source_chain_ids":{"type":"array","items":{"type":"string"},"description":"Updated list of source chain IDs."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/patterns/suggest","method":"GET","description":"Find patterns relevant to a specific task. Reads task context (title, description, agent slug) and scores patterns by keyword overlap and agent match. Returns top N patterns ranked by relevance.","inputSchema":{"type":"object","required":["task_id"],"properties":{"task_id":{"type":"string","description":"UUID of the task to find patterns for."},"limit":{"type":"number","description":"Maximum number of results to return (default 5, max 20)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/patterns/record-usage","method":"POST","description":"Record that a pattern was used and update its usage_count and success_rate. Recomputes aggregate stats from all usage history. Call after completing a task where a pattern was applied.","inputSchema":{"type":"object","required":["pattern_slug","outcome"],"properties":{"pattern_slug":{"type":"string","description":"Slug of the pattern that was used."},"outcome":{"type":"string","enum":["success","failure","partial"],"description":"Outcome of applying the pattern."},"task_id":{"type":"string","description":"Task ID where the pattern was applied."},"chain_id":{"type":"string","description":"Chain ID where the pattern was applied."},"notes":{"type":"string","description":"Optional notes about how the pattern was applied."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/postmortems","method":"GET","description":"List recent postmortems with optional filters. Returns postmortems ordered by creation date descending. Supports optional repository_id filter to scope to postmortems on chains targeting a specific repository.","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Max results to return (default 20, max 100)."},"chain_id":{"type":"string","description":"Filter to a specific chain."},"repository_id":{"type":"string","description":"Filter to postmortems on chains whose primary_repository_id matches (dev.repositories.id)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/postmortems","method":"POST","description":"Create or update (upsert) a postmortem for a chain. ON CONFLICT on chain_id updates the existing record.","inputSchema":{"type":"object","required":["chain_id"],"properties":{"chain_id":{"type":"string","description":"Chain ID this postmortem belongs to."},"root_causes":{"type":"array","items":{"type":"string"},"description":"List of root cause statements."},"what_went_well":{"type":"array","items":{"type":"string"},"description":"List of things that went well."},"what_went_poorly":{"type":"array","items":{"type":"string"},"description":"List of things that went poorly."},"action_items":{"type":"array","description":"Concrete next steps as JSON objects with at minimum a 'description' field."},"complexity_rating":{"type":"number","description":"Chain complexity rating 1-5."},"estimated_hours":{"type":"number","description":"Estimated hours for the chain."},"actual_hours":{"type":"number","description":"Actual hours taken."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/postmortems/:chainId","method":"GET","description":"Get the stored postmortem for a specific chain by chain ID.","inputSchema":{"type":"object","required":["chainId"],"properties":{"chainId":{"type":"string","description":"Chain ID to look up."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/messages","method":"GET","description":"Read agent messages with optional filters. Returns matching messages and marks them as read. Filters: agent_slug (returns messages to this agent + broadcasts), task_id, chain_id, unread_only (default true).","inputSchema":{"type":"object","properties":{"agent_slug":{"type":"string","description":"Read messages addressed to this agent slug (plus broadcasts)."},"task_id":{"type":"string","description":"Filter to messages for a specific task."},"chain_id":{"type":"string","description":"Filter to messages for a specific chain."},"unread_only":{"type":"string","enum":["true","false"],"description":"Return only unread messages. Default true."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/messages","method":"POST","description":"Send a message from one agent to another. Set to_agent_slug to target a specific agent, or omit for a broadcast to all agents. Supports task and chain associations.","inputSchema":{"type":"object","required":["from_agent_slug","message_type","content"],"properties":{"from_agent_slug":{"type":"string","description":"Sending agent's slug."},"to_agent_slug":{"type":"string","description":"Target agent slug. Omit for broadcast."},"task_id":{"type":"string","description":"Associated task ID."},"chain_id":{"type":"string","description":"Associated chain ID."},"message_type":{"type":"string","enum":["info","warning","question","dependency_update","suggestion"],"description":"Message type."},"content":{"type":"string","description":"Message body."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/approvals","method":"GET","description":"List approval requests. Defaults to pending only. Use status=all to see all requests. Returns requests with full context for the reviewer.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["pending","approved","rejected","all"],"description":"Filter by status. Defaults to 'pending'."},"task_id":{"type":"string","description":"Filter by task ID."},"chain_id":{"type":"string","description":"Filter by chain ID."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/approvals","method":"POST","description":"Create an approval request for a task. Use for high-risk tasks (pre_execution), post-completion reviews (post_execution), or critical QA findings (qa_review). Sends a notification to the human.","inputSchema":{"type":"object","required":["task_id","requested_by_agent","approval_type","reason"],"properties":{"task_id":{"type":"string","description":"Task requiring approval."},"chain_id":{"type":"string","description":"Chain this task belongs to."},"requested_by_agent":{"type":"string","description":"Slug of the requesting agent."},"approval_type":{"type":"string","enum":["pre_execution","post_execution","qa_review"],"description":"Type of approval needed."},"reason":{"type":"string","description":"Why approval is needed."},"context":{"type":"object","description":"Relevant context for the reviewer (task prompt, findings, etc.)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/approvals/:id","method":"GET","description":"Get full details of a single approval request by ID.","inputSchema":{"type":"object","required":["id"],"properties":{"id":{"type":"string","description":"Path parameter: approval request ID."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/approvals/:id","method":"PATCH","description":"Approve or reject a pending approval request. On approval, the blocked task can proceed. On rejection, the task goes back to the implementing agent with notes.","inputSchema":{"type":"object","required":["id","decision"],"properties":{"id":{"type":"string","description":"Path parameter: approval request ID."},"decision":{"type":"string","enum":["approved","rejected"],"description":"The decision."},"decided_by":{"type":"string","description":"Who decided: 'human' or an agent slug. Defaults to 'human'."},"decision_notes":{"type":"string","description":"Notes explaining the decision. Required for rejections."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/audit","method":"GET","description":"Query the agent system audit log. Supports filtering by event_type, agent_slug, chain_id, task_id, repository_id, and since (ISO datetime). Returns up to 200 entries ordered newest first.","inputSchema":{"type":"object","properties":{"event_type":{"type":"string","enum":["task_dispatched","task_claimed","task_started","task_completed","task_failed","qa_run_started","qa_run_completed","escalation","approval_requested","approval_decided","message_sent","agent_created","agent_updated"],"description":"Filter by event type."},"agent_slug":{"type":"string","description":"Filter by agent slug."},"chain_id":{"type":"string","description":"Filter by chain ID."},"task_id":{"type":"string","description":"Filter by task ID."},"repository_id":{"type":"string","description":"Filter to audit entries on chains whose primary_repository_id matches (dev.repositories.id)."},"since":{"type":"string","description":"ISO datetime — return entries after this time."},"limit":{"type":"number","description":"Max entries to return. Defaults to 50, max 200."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/agents/audit","method":"POST","description":"Append an entry to the agent audit log. Used internally by agent tools to record system events. The log is append-only — entries cannot be updated or deleted.","inputSchema":{"type":"object","required":["event_type"],"properties":{"event_type":{"type":"string","enum":["task_dispatched","task_claimed","task_started","task_completed","task_failed","qa_run_started","qa_run_completed","escalation","approval_requested","approval_decided","message_sent","agent_created","agent_updated"],"description":"The type of event being recorded."},"agent_slug":{"type":"string","description":"Agent that performed the action."},"task_id":{"type":"string","description":"Associated task ID."},"chain_id":{"type":"string","description":"Associated chain ID."},"details":{"type":"object","description":"Event-specific payload."},"tokens_used":{"type":"number","description":"Tokens consumed by this operation."},"model_used":{"type":"string","description":"Claude model tier used (opus/sonnet/haiku)."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"agents"},{"path":"/api/v1/agents/audit/summary","method":"GET","description":"Summarize agent audit activity for a time period. Returns event counts by type, agent activity breakdown, total token usage, and estimated cost.","inputSchema":{"type":"object","properties":{"period":{"type":"string","enum":["day","week","month"],"description":"Time period to summarize. Defaults to 'week'."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"agents"},{"path":"/api/v1/uploads","method":"GET","description":"List pending uploads. Filter by app (budgeting, meals, wardrobe, cpe) and status (pending, processing, completed, failed). Returns uploads with nested file metadata. Defaults to status=pending.","inputSchema":{"type":"object","properties":{"app":{"type":"string","enum":["budgeting","meals","wardrobe","cpe"],"description":"Filter by app."},"status":{"type":"string","enum":["pending","processing","completed","failed"],"description":"Filter by status. Defaults to pending."}}},"outputSchema":{"type":"object","properties":{"uploads":{"type":"array","items":{"type":"object"}},"count":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"uploads"},{"path":"/api/v1/uploads","method":"POST","description":"Create a new pending upload. Specify which app it belongs to. Optionally include the first file as base64 in the same call.","inputSchema":{"type":"object","required":["app"],"properties":{"app":{"type":"string","enum":["budgeting","meals","wardrobe","cpe"],"description":"Which app this upload is for."},"title":{"type":"string","description":"Optional label for the upload."},"notes":{"type":"string","description":"Optional context or notes."},"file_base64":{"type":"string","description":"Base64-encoded file data for the first file. Optional — you can add files later."},"file_name":{"type":"string","description":"Filename for the first file. Defaults to upload.{ext}."},"content_type":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/heic","application/pdf"],"description":"MIME type of the first file. Defaults to image/jpeg."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"uploads"},{"path":"/api/v1/uploads/:upload_id","method":"GET","description":"Get a single pending upload with all file details. Set include_images=true to fetch file contents as base64.","inputSchema":{"type":"object","properties":{"include_images":{"type":"boolean","description":"When true, fetch all files as base64. Useful for previewing upload contents."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"uploads"},{"path":"/api/v1/uploads/:upload_id","method":"DELETE","description":"Delete a pending upload and its files from both the database and storage. Only works for uploads in pending or failed status.","annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"uploads"},{"path":"/api/v1/uploads/:upload_id/files","method":"POST","description":"Add a file to an existing pending upload. The sort_order auto-increments if not specified. Upload must be in pending status.","inputSchema":{"type":"object","required":["file_base64","content_type"],"properties":{"file_base64":{"type":"string","description":"Base64-encoded file data."},"file_name":{"type":"string","description":"Filename. Defaults to upload.{ext}."},"content_type":{"type":"string","enum":["image/jpeg","image/png","image/webp","image/heic","application/pdf"],"description":"MIME type of the file."},"sort_order":{"type":"number","description":"Explicit sort order. Auto-increments from the highest existing value if omitted."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"uploads"},{"path":"/api/v1/uploads/:upload_id/process","method":"POST","description":"Mark a pending upload as completed and link it to a record. Moves files from pending/ prefix to their final per-app destination. Per-app behavior: budgeting creates receipt_attachments rows, meals updates recipe image_url, wardrobe updates item image_path, cpe updates activity proof_url. Cannot process completed uploads or uploads with 0 files.","inputSchema":{"type":"object","required":["result_type","result_id"],"properties":{"result_type":{"type":"string","description":"Type of the record created from this upload: variable_cost_transaction, recipe, wardrobe_item, cpe_activity."},"result_id":{"type":"string","description":"UUID of the record this upload was processed into."},"move_files":{"type":"boolean","description":"Whether to move files from pending/ to their final path. Defaults to true."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"uploads"},{"path":"/api/v1/uploads/:upload_id/fail","method":"POST","description":"Mark a pending upload as failed. Optionally include notes explaining the failure. Cannot fail a completed upload.","inputSchema":{"type":"object","properties":{"notes":{"type":"string","description":"Optional reason for the failure."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"uploads"},{"path":"/api/v1/economics/indicators","method":"GET","description":"List economic indicators with current values. Filter by category or activity status, or search by name.","inputSchema":{"type":"object","properties":{"category":{"type":"string","description":"Filter by category: price_index, interest_rate, exchange_rate, wage, housing, macro."},"is_active":{"type":"string","description":"Filter by active status: 'true' or 'false'. Defaults to all."},"search":{"type":"string","description":"Search indicator names (Icelandic or English)."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"economics"},{"path":"/api/v1/economics/series","method":"GET","description":"Get time-series data for one or more indicators. Returns date/value arrays per indicator.","inputSchema":{"type":"object","properties":{"slugs":{"type":"string","description":"Comma-separated indicator slugs, e.g. 'is_cpi,is_wage_index'."},"start_date":{"type":"string","description":"Start date (YYYY-MM-DD). Defaults to 1 year ago."},"end_date":{"type":"string","description":"End date (YYYY-MM-DD). Defaults to today."},"limit":{"type":"string","description":"Max data points per indicator. Defaults to 1000."}},"required":["slugs"]},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"economics"},{"path":"/api/v1/economics/latest","method":"GET","description":"Get latest values for indicators with period-over-period changes (1 month, 1 year).","inputSchema":{"type":"object","properties":{"slugs":{"type":"string","description":"Comma-separated indicator slugs. If omitted, returns all active."},"categories":{"type":"string","description":"Comma-separated categories to filter. E.g. 'price_index,exchange_rate'."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"economics"},{"path":"/api/v1/economics/ingestion-status","method":"GET","description":"Health check for data ingestion. Returns last run per source, recent failures, and stale indicators.","annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"economics"},{"path":"/api/v1/dev/projects/:project_id/archive","method":"POST","description":"Archive a dev (prompt-chain) project. Archived projects are hidden from default views but preserved for history. Cascades to incomplete chain tasks belonging to chains in this project. Parallel to tasks_projects_archive but targets the dev schema.","outputSchema":{"type":"object","properties":{"project_id":{"type":"string"},"name":{"type":"string"},"is_archived":{"type":"boolean","enum":[true]},"archived_at":{"type":"string"},"archived_task_count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/projects/:project_id/archive","method":"DELETE","description":"Unarchive a dev (prompt-chain) project. Restores an archived project to active visibility in default views. Does not un-cascade chain tasks.","outputSchema":{"type":"object","properties":{"project_id":{"type":"string"},"name":{"type":"string"},"is_archived":{"type":"boolean","enum":[false]},"archived_at":{"type":"null"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/repositories","method":"GET","description":"List repositories owned by the authenticated user. Filter by organization, primary language, stack tags (match ANY, comma-separated), or monorepo flag. Archived repositories are excluded by default; pass include_archived=true to include them. Ordered by name ascending. Supports limit/offset pagination.","inputSchema":{"type":"object","properties":{"organization_id":{"type":"string","description":"Filter to repositories belonging to this organization UUID."},"primary_language":{"type":"string","description":"Filter by primary programming language (e.g. 'TypeScript', 'Python')."},"stack_tags":{"type":"string","description":"Comma-separated list of stack tags. Returns repositories matching ANY tag."},"is_monorepo":{"type":"boolean","description":"Filter to monorepo (true) or single-package (false) repositories."},"include_archived":{"type":"boolean","description":"Include archived repositories in results. Defaults to false."},"limit":{"type":"number","description":"Max items to return (default 50, max 100)."},"offset":{"type":"number","description":"Number of items to skip (default 0)."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"default_branch":{"type":"string"},"primary_language":{"type":"string"},"stack_tags":{"type":"array","items":{"type":"string"}},"package_manager":{"type":"string"},"is_monorepo":{"type":"boolean"},"github_repo":{"type":"string"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/repositories/create","method":"POST","description":"Create a new repository record linked to an organization. Slug must be unique, lowercase, 2–64 chars, matching [a-z0-9][a-z0-9-]*. Captures build/test/lint/typecheck commands, runner config, stack tags, and QA pipeline config for use by dev agents and CI workflows.","inputSchema":{"type":"object","required":["organization_id","slug","name"],"properties":{"organization_id":{"type":"string","description":"UUID of the owning organization."},"slug":{"type":"string","description":"URL-safe identifier (lowercase, digits, hyphens; 2–64 chars)."},"name":{"type":"string","description":"Human-readable repository name."},"git_remote":{"type":"string","description":"Git remote URL (e.g. git@github.com:org/repo.git)."},"default_branch":{"type":"string","description":"Default branch name. Defaults to 'main'."},"runner_clone_path":{"type":"string","description":"Filesystem path where the runner clones this repo."},"runner_image":{"type":"string","description":"Docker image used by the runner for this repo."},"install_command":{"type":"string","description":"Command to install dependencies (e.g. 'pnpm install')."},"build_command":{"type":"string","description":"Command to build the project."},"test_command":{"type":"string","description":"Command to run tests."},"lint_command":{"type":"string","description":"Command to run linting."},"typecheck_command":{"type":"string","description":"Command to run TypeScript type checking."},"primary_language":{"type":"string","description":"Primary programming language."},"stack_tags":{"type":"array","items":{"type":"string"},"description":"Technology stack tags (e.g. ['nextjs', 'supabase', 'tailwind'])."},"package_manager":{"type":"string","description":"Package manager used (e.g. 'pnpm', 'npm', 'yarn')."},"qa_pipeline_config":{"type":"object","description":"QA pipeline configuration object."},"is_monorepo":{"type":"boolean","description":"Whether this repository is a monorepo. Defaults to false."},"github_repo":{"type":"string","description":"GitHub repository in owner/repo format."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/repositories/:repository_id","method":"GET","description":"Read a single repository by ID. Returns full config including all command fields, runner config, stack tags, and QA pipeline config. Enriched with linked products (via product_repositories), active deployments, and bound agent slugs.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"git_remote":{"type":"string"},"default_branch":{"type":"string"},"runner_clone_path":{"type":"string"},"runner_image":{"type":"string"},"install_command":{"type":"string"},"build_command":{"type":"string"},"test_command":{"type":"string"},"lint_command":{"type":"string"},"typecheck_command":{"type":"string"},"primary_language":{"type":"string"},"stack_tags":{"type":"array","items":{"type":"string"}},"package_manager":{"type":"string"},"qa_pipeline_config":{"type":"object"},"is_monorepo":{"type":"boolean"},"github_repo":{"type":"string"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"products":{"type":"array","description":"Products linked to this repository via product_repositories.","items":{"type":"object","properties":{"product_id":{"type":"string"},"monorepo_path":{"type":"string"},"is_primary":{"type":"boolean"},"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"}}}},"deployments":{"type":"array","description":"Active (non-archived) deployments targeting this repository.","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"environment":{"type":"string"},"target_kind":{"type":"string"},"url":{"type":"string"},"archived_at":{"type":"string"}}}},"agents":{"type":"array","description":"Agents bound to this repository.","items":{"type":"object","properties":{"agent_id":{"type":"string"},"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/repositories/:repository_id","method":"PATCH","description":"Update a repository. Any non-identity field can be updated, including commands, runner config, stack tags, qa_pipeline_config, and organization. Slug changes are validated for format uniqueness. At least one field required.","inputSchema":{"type":"object","properties":{"organization_id":{"type":"string","description":"Move repository to a different organization."},"slug":{"type":"string","description":"New slug (2–64 chars, [a-z0-9][a-z0-9-]*)."},"name":{"type":"string"},"git_remote":{"type":"string"},"default_branch":{"type":"string"},"runner_clone_path":{"type":"string"},"runner_image":{"type":"string"},"install_command":{"type":"string"},"build_command":{"type":"string"},"test_command":{"type":"string"},"lint_command":{"type":"string"},"typecheck_command":{"type":"string"},"primary_language":{"type":"string"},"stack_tags":{"type":"array","items":{"type":"string"}},"package_manager":{"type":"string"},"qa_pipeline_config":{"type":"object"},"is_monorepo":{"type":"boolean"},"github_repo":{"type":"string"}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/repositories/:repository_id/archive","method":"POST","description":"Archive a repository. Sets archived_at = now(). Archived repositories are hidden from default list views. Returns 400 if already archived.","outputSchema":{"type":"object","properties":{"repository_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/repositories/:repository_id/archive","method":"DELETE","description":"Unarchive a repository. Sets archived_at = null, restoring it to active visibility in list views. Returns 400 if not currently archived.","outputSchema":{"type":"object","properties":{"repository_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"null"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/deployments","method":"GET","description":"List deployments owned by the authenticated user. Filter by product, repository, environment, or target_kind. Archived deployments are excluded by default; pass include_archived=true to include them. Ordered by name ascending. Supports limit/offset pagination.","inputSchema":{"type":"object","properties":{"product_id":{"type":"string","description":"Filter to deployments belonging to this product UUID."},"repository_id":{"type":"string","description":"Filter to deployments targeting this repository UUID."},"environment":{"type":"string","enum":["production","staging","preview","development"],"description":"Filter by deployment environment."},"target_kind":{"type":"string","enum":["web","api","mobile","worker","static","other"],"description":"Filter by deployment target kind."},"include_archived":{"type":"boolean","description":"Include archived deployments in results. Defaults to false."},"limit":{"type":"number","description":"Max items to return (default 50, max 100)."},"offset":{"type":"number","description":"Number of items to skip (default 0)."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"product_id":{"type":"string"},"repository_id":{"type":"string"},"environment":{"type":"string"},"target_kind":{"type":"string"},"url":{"type":"string"},"health_check_url":{"type":"string"},"supabase_project_id":{"type":"string"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/deployments/create","method":"POST","description":"Create a new deployment record. Slug must be unique within the product, lowercase alphanumeric with hyphens, max 63 chars. Links a deployment to a specific product and repository, with environment and target_kind required. Optionally captures deploy command, branch, Vercel project ID, secrets ref, and health check URL.","inputSchema":{"type":"object","required":["product_id","repository_id","slug","name","environment","target_kind"],"properties":{"product_id":{"type":"string","description":"UUID of the product this deployment belongs to."},"repository_id":{"type":"string","description":"UUID of the repository being deployed."},"slug":{"type":"string","description":"URL-safe identifier (lowercase alphanumeric + hyphens, max 63 chars). Unique per product."},"name":{"type":"string","description":"Human-readable deployment name."},"environment":{"type":"string","enum":["production","staging","preview","development"],"description":"Deployment environment."},"target_kind":{"type":"string","enum":["web","api","mobile","worker","static","other"],"description":"What kind of artifact this deployment produces."},"url":{"type":"string","description":"Public URL of the deployed service."},"health_check_url":{"type":"string","description":"URL to ping for health checks."},"supabase_project_id":{"type":"string","description":"Associated Supabase project ID."},"deploy_command":{"type":"string","description":"Shell command used to deploy (e.g. 'vercel --prod')."},"deploy_branch":{"type":"string","description":"Git branch this deployment tracks."},"vercel_project_id":{"type":"string","description":"Vercel project ID for Vercel-hosted deployments."},"secrets_ref":{"type":"string","description":"Reference to secrets store entry for this deployment."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"product_id":{"type":"string"},"repository_id":{"type":"string"},"environment":{"type":"string"},"target_kind":{"type":"string"},"url":{"type":"string"},"health_check_url":{"type":"string"},"supabase_project_id":{"type":"string"},"deploy_command":{"type":"string"},"deploy_branch":{"type":"string"},"vercel_project_id":{"type":"string"},"secrets_ref":{"type":"string"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/deployments/:deployment_id","method":"GET","description":"Read a single deployment by ID. Returns full configuration including all command fields, Vercel project ID, secrets ref, and archive status.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"product_id":{"type":"string"},"repository_id":{"type":"string"},"environment":{"type":"string"},"target_kind":{"type":"string"},"url":{"type":"string"},"health_check_url":{"type":"string"},"supabase_project_id":{"type":"string"},"deploy_command":{"type":"string"},"deploy_branch":{"type":"string"},"vercel_project_id":{"type":"string"},"secrets_ref":{"type":"string"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/deployments/:deployment_id","method":"PATCH","description":"Update a deployment. Any non-identity field can be updated including slug, name, environment, target_kind, URL fields, deploy command, branch, Vercel project ID, and secrets ref. Slug changes are validated for format and uniqueness within the product. At least one field required.","inputSchema":{"type":"object","properties":{"slug":{"type":"string","description":"New slug (lowercase alphanumeric + hyphens, max 63 chars)."},"name":{"type":"string"},"environment":{"type":"string","enum":["production","staging","preview","development"]},"target_kind":{"type":"string","enum":["web","api","mobile","worker","static","other"]},"url":{"type":"string"},"health_check_url":{"type":"string"},"supabase_project_id":{"type":"string"},"deploy_command":{"type":"string"},"deploy_branch":{"type":"string"},"vercel_project_id":{"type":"string"},"secrets_ref":{"type":"string"}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"product_id":{"type":"string"},"repository_id":{"type":"string"},"environment":{"type":"string"},"target_kind":{"type":"string"},"url":{"type":"string"},"health_check_url":{"type":"string"},"supabase_project_id":{"type":"string"},"deploy_command":{"type":"string"},"deploy_branch":{"type":"string"},"vercel_project_id":{"type":"string"},"secrets_ref":{"type":"string"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/deployments/:deployment_id/archive","method":"POST","description":"Archive a deployment. Sets archived_at = now(). Archived deployments are hidden from default list views.","outputSchema":{"type":"object","properties":{"deployment_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/deployments/:deployment_id/archive","method":"DELETE","description":"Unarchive a deployment. Sets archived_at = null, restoring it to active visibility in list views.","outputSchema":{"type":"object","properties":{"deployment_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"null"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/agents/:agent_id/bind-repository","method":"POST","description":"Bind a repository to an agent. Creates an agent_repositories association so the agent can operate against that repository. Idempotent — safe to call if the binding already exists. Verifies both agent and repository are accessible via RLS before binding.","inputSchema":{"type":"object","required":["repository_id"],"properties":{"repository_id":{"type":"string","description":"UUID of the repository to bind to the agent."}}},"outputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"repository_id":{"type":"string"},"bound":{"type":"boolean","enum":[true]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/agents/:agent_id/unbind-repository","method":"POST","description":"Remove a repository binding from an agent. Deletes the agent_repositories row for this (agent_id, repository_id) pair. Returns 404 if the binding did not exist.","inputSchema":{"type":"object","required":["repository_id"],"properties":{"repository_id":{"type":"string","description":"UUID of the repository to unbind from the agent."}}},"outputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"repository_id":{"type":"string"},"unbound":{"type":"boolean","enum":[true]}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/agents/:agent_id/bind-organization","method":"POST","description":"Bind an organization to an agent. Creates an agent_organizations association so the agent has awareness of that organization's context. Idempotent — safe to call if the binding already exists. Verifies both agent and organization are accessible via RLS before binding.","inputSchema":{"type":"object","required":["organization_id"],"properties":{"organization_id":{"type":"string","description":"UUID of the organization to bind to the agent."}}},"outputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"organization_id":{"type":"string"},"bound":{"type":"boolean","enum":[true]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/agents/:agent_id/unbind-organization","method":"POST","description":"Remove an organization binding from an agent. Deletes the agent_organizations row for this (agent_id, organization_id) pair. Returns 404 if the binding did not exist.","inputSchema":{"type":"object","required":["organization_id"],"properties":{"organization_id":{"type":"string","description":"UUID of the organization to unbind from the agent."}}},"outputSchema":{"type":"object","properties":{"agent_id":{"type":"string"},"organization_id":{"type":"string"},"unbound":{"type":"boolean","enum":[true]}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/products","method":"GET","description":"List dev products. A product represents a shippable unit (app, service, library) within an organization. Filter by organization_id, family_slug, or status. By default only non-archived products are returned; pass include_archived=true to include them. Paginated with limit/offset. Ordered by name ascending.","inputSchema":{"type":"object","properties":{"organization_id":{"type":"string","description":"Filter by organization UUID."},"family_slug":{"type":"string","description":"Filter products belonging to a specific family slug."},"status":{"type":"string","enum":["active","paused","archived","concept"],"description":"Filter by product status."},"include_archived":{"type":"boolean","description":"Include archived products (archived_at is not null). Default false."},"limit":{"type":"number","description":"Max items to return (default 50, max 100)."},"offset":{"type":"number","description":"Number of items to skip (default 0)."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"family_slug":{"type":"string"},"status":{"type":"string"},"domain_owner_repo_id":{"type":"string"},"shared_supabase_project_id":{"type":"string"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/products/create","method":"POST","description":"Create a new dev product within an organization. A product is a shippable unit (app, service, API, library) scoped to an organization. slug must be unique within the organization, lowercase alphanumeric with hyphens, 2-64 chars. brand_tokens_override stores design-token overrides as JSON.","inputSchema":{"type":"object","required":["organization_id","slug","name"],"properties":{"organization_id":{"type":"string","description":"UUID of the organization this product belongs to."},"slug":{"type":"string","description":"URL-safe identifier, unique within the organization. Pattern: /^[a-z0-9][a-z0-9-]*$/, 2-64 chars."},"name":{"type":"string","description":"Human-readable product name."},"description":{"type":"string","description":"Optional description of the product."},"family_slug":{"type":"string","description":"Optional family grouping slug (e.g. 'ralston-is')."},"domain_owner_repo_id":{"type":"string","description":"UUID of the repository that owns the primary domain for this product."},"shared_supabase_project_id":{"type":"string","description":"Supabase project ID shared by this product, if applicable."},"brand_tokens_override":{"type":"object","description":"JSON object of brand token overrides. Defaults to {}."},"status":{"type":"string","enum":["active","paused","archived","concept"],"description":"Initial product status. Defaults to 'active'."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"family_slug":{"type":"string"},"status":{"type":"string"},"description":{"type":"string"},"domain_owner_repo_id":{"type":"string"},"shared_supabase_project_id":{"type":"string"},"brand_tokens_override":{"type":"object"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/products/:product_id","method":"GET","description":"Get a single product by UUID. Response is enriched with the product's linked repositories (M2M via product_repositories, including monorepo_path and is_primary flags per link) and all active (non-archived) deployments associated with this product.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"family_slug":{"type":"string"},"status":{"type":"string"},"description":{"type":"string"},"domain_owner_repo_id":{"type":"string"},"shared_supabase_project_id":{"type":"string"},"brand_tokens_override":{"type":"object"},"archived_at":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"repositories":{"type":"array","description":"Repositories linked to this product via the product_repositories M2M table. Each entry includes monorepo_path and is_primary flag.","items":{"type":"object","properties":{"product_id":{"type":"string"},"repository_id":{"type":"string"},"monorepo_path":{"type":"string"},"is_primary":{"type":"boolean"},"created_at":{"type":"string"},"repositories":{"type":"object","description":"Joined repository record (id, slug, name, is_monorepo, archived_at)."}}}},"deployments":{"type":"array","description":"Active (non-archived) deployments for this product.","items":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"environment":{"type":"string"},"target_kind":{"type":"string"},"url":{"type":"string"},"archived_at":{"type":"string"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/products/:product_id","method":"PATCH","description":"Partially update a product. Any provided field will be updated. At least one field must be provided. slug changes must remain unique within the organization.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"New display name."},"slug":{"type":"string","description":"New slug. Must be unique within the organization."},"description":{"type":"string","description":"Product description."},"family_slug":{"type":"string","description":"Family grouping slug."},"domain_owner_repo_id":{"type":"string","description":"UUID of the domain-owner repository."},"shared_supabase_project_id":{"type":"string","description":"Shared Supabase project ID."},"brand_tokens_override":{"type":"object","description":"Brand token overrides JSON."},"status":{"type":"string","enum":["active","paused","archived","concept"],"description":"Product status."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"organization_id":{"type":"string"},"family_slug":{"type":"string"},"status":{"type":"string"},"domain_owner_repo_id":{"type":"string"},"shared_supabase_project_id":{"type":"string"},"archived_at":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/products/:product_id/archive","method":"POST","description":"Archive a product. Sets archived_at = now() and status = 'archived'. Archived products are hidden from default list views (include_archived=true required to see them). Use DELETE on this endpoint to unarchive. Returns 400 if already archived.","outputSchema":{"type":"object","properties":{"product_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/products/:product_id/archive","method":"DELETE","description":"Unarchive a product. Clears archived_at and sets status back to 'active'. Restores product visibility in default list views. Returns 400 if not currently archived.","outputSchema":{"type":"object","properties":{"product_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"null"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/products/:product_id/link-repository","method":"POST","description":"Link a repository to a product via the product_repositories M2M join table. Supports monorepo_path to indicate which sub-path within a monorepo belongs to this product (e.g. 'apps/api-gateway'). Set is_primary=true to designate the primary repository — only one primary link is allowed per product (the partial unique index is enforced; any existing primary is automatically demoted). Re-linking an already-linked repository upserts the row, updating monorepo_path and is_primary. Verifies both product and repository exist via RLS before inserting.","inputSchema":{"type":"object","required":["repository_id"],"properties":{"repository_id":{"type":"string","description":"UUID of the repository to link to this product."},"monorepo_path":{"type":"string","description":"Path within the repository if it is a monorepo (e.g. 'apps/api-gateway'). Null for single-package repos."},"is_primary":{"type":"boolean","description":"Whether this is the primary repository for the product. Only one primary per product is allowed. Defaults to false."}}},"outputSchema":{"type":"object","properties":{"product_id":{"type":"string"},"repository_id":{"type":"string"},"monorepo_path":{"type":"string"},"is_primary":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/products/:product_id/unlink-repository","method":"POST","description":"Unlink a repository from a product, removing the M2M row from product_repositories. Returns 404 if the link does not exist. This does not delete the repository itself — only the association between product and repository.","inputSchema":{"type":"object","required":["repository_id"],"properties":{"repository_id":{"type":"string","description":"UUID of the repository to unlink from this product."}}},"outputSchema":{"type":"object","properties":{"product_id":{"type":"string"},"repository_id":{"type":"string"},"unlinked":{"type":"boolean","enum":[true]}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/organizations","method":"GET","description":"List dev organizations — the top-level tenant for the dev app. Each product, repository, and deployment belongs to exactly one organization. Supports filtering by kind (internal, client, external, personal, business) and including archived orgs.","inputSchema":{"type":"object","properties":{"kind":{"type":"string","enum":["internal","client","external","personal","business"],"description":"Filter to one organization kind."},"include_archived":{"type":"boolean","description":"Include archived orgs. Defaults to false."},"limit":{"type":"integer","description":"Max items. Default 50, max 100."},"offset":{"type":"integer","description":"Pagination offset. Default 0."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array"},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/organizations/create","method":"POST","description":"Create a new dev organization. Slug must be globally unique (lowercase alphanumeric + hyphens, 2-64 chars). Organization is the top-level tenant for products/repositories/deployments.","inputSchema":{"type":"object","required":["slug","name"],"properties":{"slug":{"type":"string","description":"Globally unique URL-safe slug (e.g., 'ralston', 'mjolnir')."},"name":{"type":"string","description":"Display name."},"legal_name":{"type":"string","description":"Legal entity name (e.g., 'Mjölnir Accounting ehf.')."},"kind":{"type":"string","enum":["internal","client","external","personal","business"],"description":"Organization category. Defaults to 'internal'."},"brand_voice":{"type":"string","description":"Brand voice guidelines."},"brand_tokens":{"type":"object","description":"Design tokens (colors, typography) as JSON."},"design_system_url":{"type":"string","description":"URL to the design system docs."},"default_locale":{"type":"string","description":"Default locale. Defaults to 'en'."},"default_qa_philosophy":{"type":"string","description":"Default QA stance (e.g., strict, balanced, iterative)."},"supabase_org_id":{"type":"string","description":"Supabase organization ID."},"vercel_team_id":{"type":"string","description":"Vercel team ID."},"github_org":{"type":"string","description":"GitHub organization login."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/organizations/:organization_id","method":"GET","description":"Read a single dev organization by ID with full details (brand tokens, locale, QA philosophy, external integration IDs).","outputSchema":{"type":"object"},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/organizations/:organization_id","method":"PATCH","description":"Update a dev organization. Any subset of fields (slug, name, legal_name, kind, brand_voice, brand_tokens, design_system_url, default_locale, default_qa_philosophy, supabase_org_id, vercel_team_id, github_org) may be supplied. Provide at least one field.","inputSchema":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"legal_name":{"type":"string"},"kind":{"type":"string","enum":["internal","client","external","personal","business"]},"brand_voice":{"type":"string"},"brand_tokens":{"type":"object"},"design_system_url":{"type":"string"},"default_locale":{"type":"string"},"default_qa_philosophy":{"type":"string"},"supabase_org_id":{"type":"string"},"vercel_team_id":{"type":"string"},"github_org":{"type":"string"}}},"outputSchema":{"type":"object"},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/organizations/:organization_id/archive","method":"POST","description":"Archive a dev organization. Sets archived_at = now(). Archived orgs are hidden from default list views but preserved for history.","outputSchema":{"type":"object","properties":{"organization_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/organizations/:organization_id/archive","method":"DELETE","description":"Unarchive a dev organization. Sets archived_at = null, restoring it to default list views.","outputSchema":{"type":"object","properties":{"organization_id":{"type":"string"},"slug":{"type":"string"},"archived_at":{"type":"null"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/jobs/:dev_job_id/runner-context","method":"GET","description":"Resolve full runner context for a dev_job: primary repository config (clone path, runner image, install/build/test/lint/typecheck commands, qa_pipeline_config, is_monorepo, github_repo), the owning organization, and a DISTINCT list of any secondary repositories the chain's tasks touch for lazy cross-repo clone. Null fields are intentional — runner_clone_path null means derive from slug; any null command means skip that stage (not fail). Use from the external Claude Code Runner service at job claim time.","outputSchema":{"type":"object","properties":{"dev_job_id":{"type":"string"},"chain_id":{"type":"string"},"chain_name":{"type":"string"},"chain_status":{"type":"string"},"branch_name":{"type":["string","null"]},"branch_prefix":{"type":["string","null"]},"target_app":{"type":["string","null"]},"organization":{"type":["object","null"],"properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string"}}},"primary_repository":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"},"name":{"type":"string"},"git_remote":{"type":["string","null"]},"default_branch":{"type":"string"},"runner_clone_path":{"type":["string","null"]},"runner_image":{"type":["string","null"]},"install_command":{"type":["string","null"]},"build_command":{"type":["string","null"]},"test_command":{"type":["string","null"]},"lint_command":{"type":["string","null"]},"typecheck_command":{"type":["string","null"]},"primary_language":{"type":["string","null"]},"stack_tags":{"type":"array","items":{"type":"string"}},"package_manager":{"type":["string","null"]},"is_monorepo":{"type":"boolean"},"github_repo":{"type":["string","null"]},"qa_pipeline_config":{"type":"object"},"archived_at":{"type":["string","null"]}}},"task_repositories":{"type":"array","description":"DISTINCT non-primary repositories this chain's tasks target, with their count. Empty for single-repo chains.","items":{"type":"object","properties":{"repository_id":{"type":"string"},"repository_slug":{"type":"string"},"chain_task_count":{"type":"number"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/resolve-supabase-project","method":"GET","description":"Resolve the Supabase project_id a chain's work should target. Walks chain → primary_repository_id → product_repositories → products → deployments (filtered by environment; defaults to 'production'). Falls back to products.shared_supabase_project_id when no deployment matches. Agents working on chains involving Supabase operations MUST use this instead of hardcoding project IDs — the correct project depends on org/product/env context and hardcoding breaks as soon as Mjölnir or another org onboards. Returns {resolved: bool, supabase_project_id, via: 'deployment'|'product_shared_supabase_project_id', reason?} — check `resolved` before trusting `supabase_project_id`.","inputSchema":{"type":"object","required":["chain_id"],"properties":{"chain_id":{"type":"string","description":"UUID of the chain whose work targets a Supabase project."},"environment":{"type":"string","enum":["production","staging","preview","development"],"description":"Which deployment environment to resolve against. Defaults to 'production'."}}},"outputSchema":{"type":"object","properties":{"chain_id":{"type":"string"},"environment":{"type":"string"},"resolved":{"type":"boolean"},"supabase_project_id":{"type":["string","null"]},"via":{"type":"string","enum":["deployment","product_shared_supabase_project_id"]},"reason":{"type":"string"},"deployment":{"type":"object","properties":{"id":{"type":"string"},"product_id":{"type":"string"},"url":{"type":["string","null"]},"target_kind":{"type":"string"}}},"product":{"type":"object","properties":{"id":{"type":"string"},"slug":{"type":"string"}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/bugs/create","method":"POST","description":"Create a bug report. Automatically sets status to 'new' and assigns the authenticated user as owner. Use this to log bugs discovered during development or by agents.","inputSchema":{"type":"object","required":["title","affected_app"],"properties":{"title":{"type":"string","description":"Short bug title."},"description":{"type":"string","description":"Detailed description of the bug."},"source_task_id":{"type":"string","description":"UUID of the originating task, if applicable."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Which app is affected."},"severity":{"type":"string","enum":["low","medium","high","critical"],"description":"Bug severity. Defaults to 'medium'."},"reproduction_steps":{"type":"string","description":"Steps to reproduce the bug."},"stack_trace":{"type":"string","description":"Stack trace, if available."},"error_message":{"type":"string","description":"Error message text, if available."},"commit_sha":{"type":"string","description":"Git commit SHA where the bug was observed."},"branch":{"type":"string","description":"Git branch where the bug was observed."},"metadata":{"type":"object","description":"Additional context as key-value pairs."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bugs/promote-task","method":"POST","description":"Promote an existing task to a bug report. Copies the task title and description, sets source_task_id to link back to the original task.","inputSchema":{"type":"object","required":["task_id","affected_app"],"properties":{"task_id":{"type":"string","description":"UUID of the task to promote to a bug."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Which app is affected."},"severity":{"type":"string","enum":["low","medium","high","critical"],"description":"Bug severity. Defaults to 'medium'."},"title":{"type":"string","description":"Override the task title for the bug. If omitted, copies from the task."},"description":{"type":"string","description":"Override the task description for the bug. If omitted, copies from the task."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"source_task_id":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bugs/list","method":"GET","description":"List bugs with optional filters. Supports filtering by status, affected_app, severity, cluster_id, source type (task vs direct), repository_id, and date range. Paginated with limit/offset.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["new","triaged","grouped","chained","fixed","wontfix"],"description":"Filter by bug status."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Filter by affected app."},"severity":{"type":"string","enum":["low","medium","high","critical"],"description":"Filter by severity."},"cluster_id":{"type":"string","description":"Filter to bugs in a specific cluster."},"repository_id":{"type":"string","description":"Filter to bugs filed against a specific repository (dev.repositories.id)."},"source":{"type":"string","enum":["task","direct"],"description":"Filter by source: 'task' (promoted from a task) or 'direct' (created directly)."},"from":{"type":"string","description":"Start of date range (ISO 8601)."},"to":{"type":"string","description":"End of date range (ISO 8601)."},"limit":{"type":"number","description":"Max items to return (default 50, max 100)."},"offset":{"type":"number","description":"Number of items to skip (default 0)."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"cluster_id":{"type":"string"},"source_task_id":{"type":"string"},"created_at":{"type":"string"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/bugs/:bug_id","method":"GET","description":"Get a single bug by ID. Includes cluster membership info and top 5 similar bugs ranked by embedding distance.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"reproduction_steps":{"type":"string"},"stack_trace":{"type":"string"},"error_message":{"type":"string"},"commit_sha":{"type":"string"},"branch":{"type":"string"},"cluster_id":{"type":"string"},"source_task_id":{"type":"string"},"metadata":{"type":"object"},"fixed_at":{"type":"string"},"fixed_by_chain_id":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"cluster":{"type":"object","description":"Cluster info if bug is grouped."},"similar_bugs":{"type":"array","description":"Top 5 similar bugs by embedding distance.","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"distance":{"type":"number"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/bugs/:bug_id","method":"PATCH","description":"Update a bug report. Any provided field will be updated.","inputSchema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"]},"severity":{"type":"string","enum":["low","medium","high","critical"]},"status":{"type":"string","enum":["new","triaged","grouped","chained","fixed","wontfix"]},"reproduction_steps":{"type":"string"},"stack_trace":{"type":"string"},"error_message":{"type":"string"},"commit_sha":{"type":"string"},"branch":{"type":"string"},"cluster_id":{"type":"string","description":"Set or clear cluster assignment."},"metadata":{"type":"object"}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"severity":{"type":"string"},"affected_app":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bugs/:bug_id","method":"DELETE","description":"Delete a bug report.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bugs/:bug_id/triage","method":"POST","description":"Triage a single bug. Primary mode: provide pre-computed embedding and/or inferred severity/affected_app (from Claude Code or a scheduled task). Fallback: if no fields provided and OPENAI_API_KEY/ANTHROPIC_API_KEY are configured, auto-computes via APIs. Sets status to 'triaged'.","inputSchema":{"type":"object","properties":{"embedding":{"type":"array","items":{"type":"number"},"description":"Pre-computed 1536-dim embedding vector (text-embedding-3-small format)."},"severity":{"type":"string","enum":["low","medium","high","critical"],"description":"Inferred severity to set on the bug."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Inferred affected app to set on the bug."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"severity":{"type":"string"},"affected_app":{"type":"string"},"updated_at":{"type":"string"},"embedding_set":{"type":"boolean"},"embedding_source":{"type":"string","enum":["provided","auto","none"]}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bugs/triage-run","method":"POST","description":"Batch triage run. Primary mode: provide a 'bugs' array with pre-computed data [{bug_id, embedding?, severity?, affected_app?}]. Fallback: if no bugs array and API keys are configured, auto-processes all status='new' bugs. Idempotent — only processes 'new' bugs.","inputSchema":{"type":"object","properties":{"bugs":{"type":"array","description":"Array of pre-computed triage data. Each item: {bug_id (required), embedding? (1536-dim), severity?, affected_app?}. If omitted, falls back to automatic mode if API keys are configured.","items":{"type":"object","required":["bug_id"],"properties":{"bug_id":{"type":"string"},"embedding":{"type":"array","items":{"type":"number"}},"severity":{"type":"string"},"affected_app":{"type":"string"}}}},"batch_size":{"type":"number","description":"Max bugs to process in automatic mode (default 50, max 100). Ignored in manual mode."}}},"outputSchema":{"type":"object","properties":{"processed":{"type":"number"},"succeeded":{"type":"number"},"failed":{"type":"number"},"skipped":{"type":"number"},"errors":{"type":"array","items":{"type":"string"}},"message":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bugs/:bug_id/mark-fixed","method":"POST","description":"Mark a bug as fixed. Optionally link the chain that fixed it. Sets status to 'fixed' and fixed_at to now.","inputSchema":{"type":"object","properties":{"chain_id":{"type":"string","description":"UUID of the prompt chain project that fixed this bug (soft reference to tasks.projects)."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["fixed"]},"fixed_at":{"type":"string"},"fixed_by_chain_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/list","method":"GET","description":"List bug clusters with optional filters. Defaults to showing 'proposed' clusters. Supports filtering by status, affected_app, severity, repository_id, with pagination.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["proposed","approved","chained","resolved","dismissed"],"description":"Filter by cluster status. Defaults to 'proposed'."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Filter by affected app."},"severity":{"type":"string","enum":["low","medium","high","critical"],"description":"Filter by severity."},"repository_id":{"type":"string","description":"Filter to clusters scoped to a specific repository (dev.repositories.id)."},"limit":{"type":"number","description":"Max items to return (default 50, max 100)."},"offset":{"type":"number","description":"Number of items to skip (default 0)."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"proposed_by":{"type":"string"},"member_count":{"type":"number"},"created_at":{"type":"string"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id","method":"GET","description":"Get a single bug cluster by ID with full member list (bugs with confidence scores) and linked chain status if chained.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"proposed_by":{"type":"string"},"approved_at":{"type":"string"},"approved_by":{"type":"string"},"chain_id":{"type":"string"},"dismissed_reason":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"members":{"type":"array","description":"Bugs in this cluster with confidence scores.","items":{"type":"object","properties":{"bug_id":{"type":"string"},"title":{"type":"string"},"severity":{"type":"string"},"status":{"type":"string"},"confidence":{"type":"number"},"added_by":{"type":"string"},"added_at":{"type":"string"}}}},"chain":{"type":"object","description":"Linked chain project info, if status is chained."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id","method":"PATCH","description":"Update a bug cluster's title, summary, severity, or affected_app.","inputSchema":{"type":"object","properties":{"title":{"type":"string"},"summary":{"type":"string"},"severity":{"type":"string","enum":["low","medium","high","critical"]},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"]}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"severity":{"type":"string"},"affected_app":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id/members","method":"POST","description":"Add a bug to a cluster manually. Sets added_by to 'human'. Only allowed when cluster is in proposed or approved status.","inputSchema":{"type":"object","required":["bug_id"],"properties":{"bug_id":{"type":"string","description":"UUID of the bug to add to this cluster."},"confidence":{"type":"number","description":"Optional confidence score (0-1)."}}},"outputSchema":{"type":"object","properties":{"cluster_id":{"type":"string"},"bug_id":{"type":"string"},"added_by":{"type":"string"},"confidence":{"type":"number"},"added_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id/members/:bug_id","method":"DELETE","description":"Remove a bug from a cluster. Only allowed when cluster is in proposed or approved status.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/propose","method":"POST","description":"Propose bug clusters. Primary mode: provide a 'clusters' array with pre-computed proposals [{title, summary?, affected_app, severity, members: [{bug_id, confidence?}]}]. Fallback: if no clusters array and ANTHROPIC_API_KEY is configured, uses pgvector pairwise similarity + LLM confirmation to auto-propose. Bugs stay status='triaged' — only join rows created.","inputSchema":{"type":"object","properties":{"clusters":{"type":"array","description":"Pre-computed cluster proposals. Each needs title, affected_app, severity, and members array with at least 2 bugs.","items":{"type":"object","required":["title","affected_app","severity","members"],"properties":{"title":{"type":"string"},"summary":{"type":"string"},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"]},"severity":{"type":"string","enum":["low","medium","high","critical"]},"members":{"type":"array","items":{"type":"object","required":["bug_id"],"properties":{"bug_id":{"type":"string"},"confidence":{"type":"number"}}}}}}},"force":{"type":"boolean","description":"Force auto-clustering even if pool size is below minimum (default false)."},"similarity_threshold":{"type":"number","description":"Cosine similarity threshold for auto-clustering (default 0.78)."},"min_pool_size":{"type":"number","description":"Minimum eligible bugs for auto-clustering (default 3)."}}},"outputSchema":{"type":"object","properties":{"proposed_count":{"type":"number"},"total_bugs_grouped":{"type":"number"},"skipped":{"type":"number"},"reason":{"type":"string"},"clusters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"member_count":{"type":"number"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id/approve","method":"POST","description":"Approve a proposed cluster. Sets status to 'approved', records approved_at and approved_by. Updates all member bugs to status 'grouped'. Returns 409 if any member bug is already in another approved cluster.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["approved"]},"approved_at":{"type":"string"},"approved_by":{"type":"string"},"bugs_updated":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id/dismiss","method":"POST","description":"Dismiss a proposed cluster. Sets status to 'dismissed' with a reason. Clears cluster_id from member bugs and returns them to 'triaged' status.","inputSchema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Reason for dismissing the cluster."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["dismissed"]},"dismissed_reason":{"type":"string"},"bugs_returned":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id/preview-chain","method":"GET","description":"Preview the prompt chain plan for an approved bug cluster without committing. Returns the proposed plan (LLM-generated or template) for review before creation.","outputSchema":{"type":"object","properties":{"cluster":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"affected_app":{"type":"string"},"severity":{"type":"string"},"bug_count":{"type":"number"}}},"plan":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"prompt_content":{"type":"string"},"verification_items":{"type":"array","items":{"type":"string"}},"depends_on":{"type":"array","items":{"type":"string"}},"risk_level":{"type":"string"}}}},"plan_source":{"type":"string","enum":["llm","template"],"description":"How the plan was generated."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id/create-chain","method":"POST","description":"Create a prompt chain from an approved bug cluster. Optionally provide an override_plan (from the preview-chain response, edited). Calls tasks_prompt_chain_create internally, sets cluster status to 'chained', and updates member bugs to 'chained'.","inputSchema":{"type":"object","properties":{"override_plan":{"type":"array","description":"Optional edited plan to use instead of auto-generating. Each item needs name and prompt_content.","items":{"type":"object","required":["name","prompt_content"],"properties":{"name":{"type":"string"},"prompt_content":{"type":"string"},"verification_items":{"type":"array","items":{"type":"string"}},"depends_on":{"type":"array","items":{"type":"string"}},"risk_level":{"type":"string","enum":["low","medium","high"]}}}}}},"outputSchema":{"type":"object","properties":{"chain_id":{"type":"string"},"project_id":{"type":"string"},"cluster_id":{"type":"string"},"task_count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/bug-clusters/:cluster_id/unlink-chain","method":"POST","description":"Detach a failed chain from a cluster. Resets cluster to 'approved' and member bugs to 'grouped', allowing a new chain to be created.","outputSchema":{"type":"object","properties":{"cluster_id":{"type":"string"},"status":{"type":"string","enum":["approved"]},"previous_chain_id":{"type":"string"},"bugs_reset":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-ideas/create","method":"POST","description":"Create a feature idea. Automatically sets status to 'new' and assigns the authenticated user as owner. Use this to log feature requests discovered during development, from feedback, or by agents.","inputSchema":{"type":"object","required":["title","affected_app"],"properties":{"title":{"type":"string","description":"Short feature idea title."},"description":{"type":"string","description":"Detailed description of the feature idea."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Which app this feature idea is for."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Feature priority. Defaults to 'medium'."},"metadata":{"type":"object","description":"Additional context as key-value pairs."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-ideas/promote-task","method":"POST","description":"Promote an existing task to a feature idea. Copies the task title and description, links back to the source task.","inputSchema":{"type":"object","required":["task_id","affected_app"],"properties":{"task_id":{"type":"string","description":"UUID of the task to promote to a feature idea."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Which app this feature idea is for."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Feature priority. Defaults to 'medium'."},"title":{"type":"string","description":"Override the task title. If omitted, copies from the task."},"description":{"type":"string","description":"Override the task description. If omitted, copies from the task."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-ideas/list","method":"GET","description":"List feature ideas with optional filters. Supports filtering by status, affected_app, priority, cluster_id, source type (task|direct|feedback), and date range. Paginated with limit/offset.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["new","triaged","grouped","chained","shipped","wontfix"],"description":"Filter by feature idea status."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Filter by affected app."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Filter by priority."},"cluster_id":{"type":"string","description":"Filter to feature ideas in a specific cluster."},"source":{"type":"string","enum":["task","direct","feedback"],"description":"Filter by source: 'task' (promoted from a task), 'feedback' (sourced from a feedback item), or 'direct' (created directly)."},"from":{"type":"string","description":"Start of date range (ISO 8601)."},"to":{"type":"string","description":"End of date range (ISO 8601)."},"limit":{"type":"number","description":"Max items to return (default 50, max 100)."},"offset":{"type":"number","description":"Number of items to skip (default 0)."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"cluster_id":{"type":"string"},"source_feedback_item_id":{"type":"string"},"created_at":{"type":"string"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/feature-ideas/:feature_idea_id","method":"GET","description":"Get a single feature idea by ID. Includes cluster membership info and top 5 similar feature ideas ranked by embedding distance.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"cluster_id":{"type":"string"},"source_feedback_item_id":{"type":"string"},"metadata":{"type":"object"},"shipped_at":{"type":"string"},"shipped_by_chain_id":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"cluster":{"type":"object","description":"Cluster info if grouped."},"similar_feature_ideas":{"type":"array","description":"Top 5 similar feature ideas by embedding distance.","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"distance":{"type":"number"}}}}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/feature-ideas/:feature_idea_id","method":"PATCH","description":"Update a feature idea. Any provided field will be updated.","inputSchema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"]},"priority":{"type":"string","enum":["low","medium","high","critical"]},"status":{"type":"string","enum":["new","triaged","grouped","chained","shipped","wontfix"]},"cluster_id":{"type":"string","description":"Set or clear cluster assignment."},"metadata":{"type":"object"}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"priority":{"type":"string"},"affected_app":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-ideas/:feature_idea_id","method":"DELETE","description":"Hard delete a feature idea.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-ideas/:feature_idea_id/triage","method":"POST","description":"Triage a single feature idea. Accepts a pre-computed embedding (1536-dim), priority, and affected_app. Sets status to 'triaged'. If no fields are provided and API keys are configured, falls back to automatic embedding generation and field inference.","inputSchema":{"type":"object","properties":{"embedding":{"type":"array","items":{"type":"number"},"description":"Pre-computed 1536-dimension embedding vector for the feature idea."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Inferred or manually set priority for the feature idea."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Which app this feature idea is for."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"priority":{"type":"string"},"affected_app":{"type":"string"},"updated_at":{"type":"string"},"embedding_set":{"type":"boolean","description":"Whether an embedding is now stored for this feature idea."},"embedding_source":{"type":"string","enum":["provided","auto","none"],"description":"How the embedding was set: provided by caller, auto-generated, or not set."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-ideas/triage-run","method":"POST","description":"Batch triage feature ideas with status='new'. Two modes: (1) Manual — provide a feature_ideas array with pre-computed embeddings and/or inferred fields; (2) Automatic — omit the array and the server fetches new feature ideas and processes them via OpenAI and Anthropic if API keys are configured.","inputSchema":{"type":"object","properties":{"feature_ideas":{"type":"array","description":"Pre-computed triage data for each feature idea. If omitted, automatic mode is used.","items":{"type":"object","required":["feature_idea_id"],"properties":{"feature_idea_id":{"type":"string","description":"UUID of the feature idea to triage."},"embedding":{"type":"array","items":{"type":"number"},"description":"Pre-computed 1536-dimension embedding vector."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Inferred or manually set priority."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Which app this feature idea is for."}}}},"batch_size":{"type":"number","description":"Max feature ideas to process in automatic mode (default 50, max 100)."}}},"outputSchema":{"type":"object","properties":{"processed":{"type":"number"},"succeeded":{"type":"number"},"failed":{"type":"number"},"skipped":{"type":"number"},"errors":{"type":"array","items":{"type":"string"}},"message":{"type":"string","description":"Informational message, present when no items were processed."}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-ideas/:feature_idea_id/mark-shipped","method":"POST","description":"Mark a feature idea as shipped. Optionally link the chain that shipped it. Sets status to 'shipped' and shipped_at to now.","inputSchema":{"type":"object","properties":{"chain_id":{"type":"string","description":"UUID of the prompt chain project that shipped this feature idea (soft reference to tasks.projects)."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["shipped"]},"shipped_at":{"type":"string"},"shipped_by_chain_id":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/list","method":"GET","description":"List feature clusters with optional filters. Defaults to showing 'proposed' clusters. Supports filtering by status, affected_app, priority, with pagination.","inputSchema":{"type":"object","properties":{"status":{"type":"string","enum":["proposed","approved","chained","resolved","dismissed"],"description":"Filter by cluster status. Defaults to 'proposed'."},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"],"description":"Filter by affected app."},"priority":{"type":"string","enum":["low","medium","high","critical"],"description":"Filter by priority."},"limit":{"type":"number","description":"Max items to return (default 50, max 100)."},"offset":{"type":"number","description":"Number of items to skip (default 0)."}}},"outputSchema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"proposed_by":{"type":"string"},"member_count":{"type":"number"},"created_at":{"type":"string"}}}},"total":{"type":"number"},"limit":{"type":"number"},"offset":{"type":"number"}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id","method":"GET","description":"Get a single feature cluster by ID with full member list (feature ideas with confidence scores) and linked chain status if chained.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"proposed_by":{"type":"string"},"approved_at":{"type":"string"},"approved_by":{"type":"string"},"chain_id":{"type":"string"},"dismissed_reason":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"members":{"type":"array","description":"Feature ideas in this cluster with confidence scores.","items":{"type":"object","properties":{"feature_idea_id":{"type":"string"},"title":{"type":"string"},"priority":{"type":"string"},"status":{"type":"string"},"confidence":{"type":"number"},"added_by":{"type":"string"},"added_at":{"type":"string"}}}},"chain":{"type":"object","description":"Linked chain info, if status is chained."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id","method":"PATCH","description":"Update a feature cluster's title, summary, priority, or affected_app.","inputSchema":{"type":"object","properties":{"title":{"type":"string"},"summary":{"type":"string"},"priority":{"type":"string","enum":["low","medium","high","critical"]},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"]}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"priority":{"type":"string"},"affected_app":{"type":"string"},"updated_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id/members","method":"POST","description":"Add a feature idea to a cluster manually. Sets added_by to 'human'. Only allowed when cluster is in proposed or approved status.","inputSchema":{"type":"object","required":["feature_idea_id"],"properties":{"feature_idea_id":{"type":"string","description":"UUID of the feature idea to add to this cluster."},"confidence":{"type":"number","description":"Optional confidence score (0-1)."}}},"outputSchema":{"type":"object","properties":{"cluster_id":{"type":"string"},"feature_idea_id":{"type":"string"},"added_by":{"type":"string"},"confidence":{"type":"number"},"added_at":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id/members/:feature_idea_id","method":"DELETE","description":"Remove a feature idea from a cluster. Only allowed when cluster is in proposed or approved status.","outputSchema":{"type":"object","properties":{"deleted":{"type":"boolean"}}},"annotations":{"readOnlyHint":false,"destructiveHint":true,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/propose","method":"POST","description":"Propose feature clusters. Primary mode: provide a 'clusters' array with pre-computed proposals [{title, summary?, affected_app, priority, members: [{feature_idea_id, confidence?}]}]. Fallback: if no clusters array and ANTHROPIC_API_KEY is configured, uses pgvector pairwise similarity + LLM confirmation to auto-propose. Feature ideas stay status='triaged' — only join rows created.","inputSchema":{"type":"object","properties":{"clusters":{"type":"array","description":"Pre-computed cluster proposals. Each needs title, affected_app, priority, and members array with at least 2 feature ideas.","items":{"type":"object","required":["title","affected_app","priority","members"],"properties":{"title":{"type":"string"},"summary":{"type":"string"},"affected_app":{"type":"string","enum":["tasks","fitness","meals","budgeting","wardrobe","notes","cpe","hub","dev","economics","mcp","gateway","other"]},"priority":{"type":"string","enum":["low","medium","high","critical"]},"members":{"type":"array","items":{"type":"object","required":["feature_idea_id"],"properties":{"feature_idea_id":{"type":"string"},"confidence":{"type":"number"}}}}}}},"force":{"type":"boolean","description":"Force auto-clustering even if pool size is below minimum (default false)."},"similarity_threshold":{"type":"number","description":"Cosine similarity threshold for auto-clustering (default 0.78)."},"min_pool_size":{"type":"number","description":"Minimum eligible feature ideas for auto-clustering (default 3)."}}},"outputSchema":{"type":"object","properties":{"proposed_count":{"type":"number"},"total_ideas_grouped":{"type":"number"},"skipped":{"type":"number"},"reason":{"type":"string"},"clusters":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"member_count":{"type":"number"}}}}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id/approve","method":"POST","description":"Approve a proposed feature cluster. Sets status to 'approved', records approved_at and approved_by. Updates all member feature ideas to status 'grouped'. Returns 409 if any member feature idea is already in another approved cluster.","outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["approved"]},"approved_at":{"type":"string"},"approved_by":{"type":"string"},"ideas_updated":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id/dismiss","method":"POST","description":"Dismiss a proposed feature cluster. Sets status to 'dismissed' with a reason. Clears cluster_id from member feature ideas and returns them to 'triaged' status.","inputSchema":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","description":"Reason for dismissing the cluster."}}},"outputSchema":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["dismissed"]},"dismissed_reason":{"type":"string"},"ideas_returned":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id/preview-chain","method":"GET","description":"Preview the prompt chain plan for an approved feature cluster without committing. Returns the proposed plan (LLM-generated or template) for review before creation.","outputSchema":{"type":"object","properties":{"cluster":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"affected_app":{"type":"string"},"priority":{"type":"string"},"feature_count":{"type":"number"}}},"plan":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"prompt_content":{"type":"string"},"verification_items":{"type":"array","items":{"type":"string"}},"depends_on":{"type":"array","items":{"type":"string"}},"risk_level":{"type":"string"}}}},"plan_source":{"type":"string","enum":["llm","template"],"description":"How the plan was generated."}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id/create-chain","method":"POST","description":"Create a prompt chain from an approved feature cluster. Optionally provide an override_plan (from the preview-chain response, edited). Calls tasks_prompt_chain_create internally, sets cluster status to 'chained', updates member feature ideas to 'chained', and sets promoted_chain_id on linked feedback items.","inputSchema":{"type":"object","properties":{"override_plan":{"type":"array","description":"Optional edited plan to use instead of auto-generating. Each item needs name and prompt_content.","items":{"type":"object","required":["name","prompt_content"],"properties":{"name":{"type":"string"},"prompt_content":{"type":"string"},"verification_items":{"type":"array","items":{"type":"string"}},"depends_on":{"type":"array","items":{"type":"string"}},"risk_level":{"type":"string","enum":["low","medium","high"]}}}}}},"outputSchema":{"type":"object","properties":{"chain_id":{"type":"string"},"project_id":{"type":"string"},"cluster_id":{"type":"string"},"task_count":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/dev/feature-clusters/:cluster_id/unlink-chain","method":"POST","description":"Detach a failed chain from a feature cluster. Resets cluster to 'approved' and member feature ideas to 'grouped', clears promoted_chain_id on linked feedback items, allowing a new chain to be created.","outputSchema":{"type":"object","properties":{"cluster_id":{"type":"string"},"status":{"type":"string","enum":["approved"]},"previous_chain_id":{"type":"string"},"ideas_reset":{"type":"number"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"write","app":"dev"},{"path":"/api/v1/hub/kronan/token-status","method":"GET","description":"Get the current status of the family Krónan API token. Returns whether a token is configured, the verified identity, and when it was last verified. Read-only — does not contact Krónan.","outputSchema":{"type":"object","properties":{"isConfigured":{"type":"boolean"},"tokenHint":{"type":["string","null"]},"lastVerifiedAt":{"type":["string","null"]},"verifiedIdentityName":{"type":["string","null"]},"verifiedIdentityType":{"type":["string","null"],"enum":["user","customer_group",null]}}},"annotations":{"readOnlyHint":true,"destructiveHint":false,"openWorldHint":false},"accessLevel":"public","tier":"read","app":"hub"},{"path":"/api/v1/hub/kronan/token-test","method":"POST","description":"Test the family Krónan API token by calling Krónan /me/. Updates last_verified_at on success. Admin-only.","outputSchema":{"type":"object","properties":{"ok":{"type":"boolean"},"identity":{"type":"object","properties":{"name":{"type":"string"},"type":{"type":"string","enum":["user","customer_group"]}}},"lastVerifiedAt":{"type":"string"},"error":{"type":"string"}}},"annotations":{"readOnlyHint":false,"destructiveHint":false,"openWorldHint":true},"accessLevel":"public","tier":"write","app":"hub"}]}}