{"openapi":"3.1.0","info":{"title":"My Yarn Stash","description":"My Yarn Stash - Track yarn usage with AI-powered label extraction","version":"0.1.0"},"paths":{"/auth/login":{"get":{"summary":"Login","description":"Endpoint to initiate the login process.\nOptionally accepts a 'return_to' query parameter and passes it as part of the app state.\nRedirects the user to the Auth0 authorization URL.","operationId":"login_auth_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/auth/callback":{"get":{"summary":"Callback","description":"Endpoint to handle the callback after Auth0 authentication.\nProcesses the callback URL and completes the login or connected account flow.\nRedirects the user to a post-login URL based on appState or a default.","operationId":"callback_auth_callback_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/auth/logout":{"get":{"summary":"Logout","description":"Endpoint to handle logout.\nClears the session cookie (if applicable) and generates a logout URL,\nthen redirects the user to Auth0's logout endpoint.","operationId":"logout_auth_logout_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/auth/backchannel-logout":{"post":{"summary":"Backchannel Logout","description":"Endpoint to process backchannel logout notifications.\nExpects a JSON body with a 'logout_token'.\nReturns 204 No Content on success.","operationId":"backchannel_logout_auth_backchannel_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/yarn/":{"get":{"tags":["yarn"],"summary":"List Yarns","description":"Get paginated yarns as HTMX partial (excluding soft-deleted).\n\nOptional filters:\n- weight: Filter by weight code (0-7)\n- in_stock: \"true\" for quantity > 0, \"false\" for quantity = 0\n- page: Page number (1-indexed, default 1)\n- limit: Items per page (default 5, max 100)","operationId":"list_yarns_yarn__get","parameters":[{"name":"weight","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weight"}},{"name":"in_stock","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"In Stock"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["yarn"],"summary":"Create Yarn","description":"Create a new yarn entry.\n\nSupports three flows:\n1. With images + use_ai=True: Process images and trigger AI extraction\n2. With images + use_ai=False: Process images but skip AI (status=\"skipped\")\n3. Without images: Create yarn with manual fields only\n\nReturns appropriate HTMX partial based on the flow.","operationId":"create_yarn_yarn__post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_yarn_yarn__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}":{"get":{"tags":["yarn"],"summary":"Get Yarn","description":"Get yarn detail page.","operationId":"get_yarn_yarn__yarn_id__get","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["yarn"],"summary":"Delete Yarn","description":"Soft delete a yarn entry (mark as deleted but keep in database).","operationId":"delete_yarn_yarn__yarn_id__delete","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["yarn"],"summary":"Update Yarn","description":"Update yarn information and optionally add new images.","operationId":"update_yarn_yarn__yarn_id__put","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/edit":{"get":{"tags":["yarn"],"summary":"Get Yarn Edit","description":"Get yarn detail page in edit mode.","operationId":"get_yarn_edit_yarn__yarn_id__edit_get","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/bulk/check":{"post":{"tags":["yarn"],"summary":"Check Bulk Delete","description":"Pre-flight check for bulk delete - categorize yarns into deletable vs attached-to-project.","operationId":"check_bulk_delete_yarn_bulk_check_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/yarn/bulk":{"delete":{"tags":["yarn"],"summary":"Bulk Delete Yarns","description":"Bulk soft delete yarns (only those not attached to projects).","operationId":"bulk_delete_yarns_yarn_bulk_delete","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/yarn/bulk/finish-ball/check":{"post":{"tags":["yarn"],"summary":"Check Bulk Finish Ball","description":"Pre-flight check for bulk finish ball - categorize yarns by available quantity.","operationId":"check_bulk_finish_ball_yarn_bulk_finish_ball_check_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/yarn/bulk/finish-ball":{"post":{"tags":["yarn"],"summary":"Bulk Finish Ball","description":"Bulk finish ball - decrement quantity by 1 for each selected yarn.","operationId":"bulk_finish_ball_yarn_bulk_finish_ball_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/yarn/{yarn_id}/status":{"get":{"tags":["yarn"],"summary":"Get Yarn Status","description":"Check extraction status for HTMX polling.\n\nReturns appropriate card template based on status.","operationId":"get_yarn_status_yarn__yarn_id__status_get","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/extract":{"post":{"tags":["yarn"],"summary":"Extract Yarn Info","description":"Trigger AI extraction for a yarn entry.\n\nWorks for both:\n- \"Try Again\" - retry after failed/timeout extraction\n- \"Extract Now\" - first-time extraction for yarns with status=\"skipped\"\n\nResets status to \"processing\" and spawns background extraction task.","operationId":"extract_yarn_info_yarn__yarn_id__extract_post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/approve-suggestions":{"post":{"tags":["yarn"],"summary":"Approve Suggestions","description":"Apply AI-extracted suggestions to the yarn record.\n\nAccepts a list of field names that the user has approved.\nOnly approved fields are applied to the yarn; others are ignored.\nAfter approval, extraction status changes to 'completed'.","operationId":"approve_suggestions_yarn__yarn_id__approve_suggestions_post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/reject-suggestions":{"post":{"tags":["yarn"],"summary":"Reject Suggestions","description":"Reject all AI suggestions and clear them.\n\nSets status to 'skipped' so user can manually enter data.\nUser can later trigger extraction again if desired.","operationId":"reject_suggestions_yarn__yarn_id__reject_suggestions_post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/projects/{project_id}":{"post":{"tags":["yarn"],"summary":"Add Yarn To Project From Yarn","description":"Add this yarn to a project (from yarn detail page).","operationId":"add_yarn_to_project_from_yarn_yarn__yarn_id__projects__project_id__post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["yarn"],"summary":"Remove Yarn From Project From Yarn","description":"Remove this yarn from a project (from yarn detail page).","operationId":"remove_yarn_from_project_from_yarn_yarn__yarn_id__projects__project_id__delete","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}},{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/image/{image_id}":{"delete":{"tags":["yarn"],"summary":"Delete Yarn Image","description":"Soft delete a yarn image.","operationId":"delete_yarn_image_yarn__yarn_id__image__image_id__delete","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}},{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/image/{image_id}/set-cover":{"post":{"tags":["yarn"],"summary":"Set Yarn Cover Image","description":"Set an image as the cover image for a yarn (Plus feature).","operationId":"set_yarn_cover_image_yarn__yarn_id__image__image_id__set_cover_post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}},{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/skein-usage":{"post":{"tags":["yarn"],"summary":"Update Skein Usage","description":"Update partial usage tracking for the active skein.\n\nAccepts either:\n- percent_used: 25, 50, or 75 (calculates remaining from label specs)\n- remaining_grams: custom value (calculates remaining_yardage proportionally)\n\nIf yarn has no grams set, only tracks remaining_grams (not yardage).","operationId":"update_skein_usage_yarn__yarn_id__skein_usage_post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_update_skein_usage_yarn__yarn_id__skein_usage_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/yarn/{yarn_id}/skein-usage/reset":{"post":{"tags":["yarn"],"summary":"Reset Skein Usage","description":"Reset skein usage tracking to full (untouched skein).\nSets remaining_grams and remaining_yardage back to None.","operationId":"reset_skein_usage_yarn__yarn_id__skein_usage_reset_post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/":{"get":{"tags":["projects"],"summary":"List Projects","description":"Get paginated projects as HTMX partial or redirect to dashboard (excluding soft-deleted).\n\nOptional filters:\n- project_type: Filter by project type\n- status: Filter by project status\n- page: Page number (1-indexed, default 1)\n- limit: Items per page (default 5, max 100)","operationId":"list_projects_projects__get","parameters":[{"name":"project_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Project Type"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","default":1,"title":"Page"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":5,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["projects"],"summary":"Create Project","description":"Create a new project with optional image uploads.","operationId":"create_project_projects__post","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_create_project_projects__post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}":{"get":{"tags":["projects"],"summary":"Get Project","description":"Get project detail page.","operationId":"get_project_projects__project_id__get","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["projects"],"summary":"Update Project","description":"Update project information and optionally upload new images.","operationId":"update_project_projects__project_id__put","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["projects"],"summary":"Delete Project","description":"Soft delete a project.","operationId":"delete_project_projects__project_id__delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/edit":{"get":{"tags":["projects"],"summary":"Get Project Edit","description":"Get project detail page in edit mode.","operationId":"get_project_edit_projects__project_id__edit_get","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/bulk/check":{"post":{"tags":["projects"],"summary":"Check Bulk Delete Projects","description":"Pre-flight check for bulk delete - returns user's non-SYSTEM projects for confirmation.","operationId":"check_bulk_delete_projects_projects_bulk_check_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/projects/bulk":{"delete":{"tags":["projects"],"summary":"Bulk Delete Projects","description":"Bulk soft delete projects.","operationId":"bulk_delete_projects_projects_bulk_delete","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/projects/bulk/finish/check":{"post":{"tags":["projects"],"summary":"Check Bulk Finish Projects","description":"Pre-flight check for bulk finish - categorize projects into finishable vs skipped.","operationId":"check_bulk_finish_projects_projects_bulk_finish_check_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/projects/bulk/finish":{"post":{"tags":["projects"],"summary":"Bulk Finish Projects","description":"Bulk finish projects - mark as completed.","operationId":"bulk_finish_projects_projects_bulk_finish_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/projects/bulk/frog/check":{"post":{"tags":["projects"],"summary":"Check Bulk Frog Projects","description":"Pre-flight check for bulk frog - categorize projects and warn about yarn restoration.","operationId":"check_bulk_frog_projects_projects_bulk_frog_check_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/projects/bulk/frog":{"post":{"tags":["projects"],"summary":"Bulk Frog Projects","description":"Bulk frog projects - rip out and restore yarn to stash.","operationId":"bulk_frog_projects_projects_bulk_frog_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/projects/{project_id}/frog":{"post":{"tags":["projects"],"summary":"Frog Project","description":"Frog a project - rip it out and return yarn to stash.\n\n- Sets project status to 'frogged' and records frogged_at timestamp\n- Marks all usage records as reversed (is_reversed=True)\n- Restores yarn quantities to stash\n- Project enters 10-day reactivation window","operationId":"frog_project_projects__project_id__frog_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/unfrog":{"post":{"tags":["projects"],"summary":"Unfrog Project","description":"Unfrog (reactivate) a frogged project within the 10-day window.\n\n- Validates project is within reactivation window\n- Sets status back to 'in_progress'\n- Re-applies reversed usage records (decrements stash again)\n- Clears frogged_at","operationId":"unfrog_project_projects__project_id__unfrog_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/finish":{"post":{"tags":["projects"],"summary":"Finish Project","description":"Mark a project as finished/completed.\n\n- Sets project status to 'completed' and records finished_at timestamp\n- Does not affect yarn usage (unlike frogging)","operationId":"finish_project_projects__project_id__finish_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/unfinish":{"post":{"tags":["projects"],"summary":"Unfinish Project","description":"Unmark a project as finished - set back to in_progress.\n\n- Sets status back to 'in_progress'\n- Clears finished_at timestamp","operationId":"unfinish_project_projects__project_id__unfinish_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/yarns/{yarn_id}":{"post":{"tags":["projects"],"summary":"Add Yarn To Project","description":"Add a yarn to a project.","operationId":"add_yarn_to_project_projects__project_id__yarns__yarn_id__post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}},{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["projects"],"summary":"Remove Yarn From Project","description":"Remove a yarn from a project.","operationId":"remove_yarn_from_project_projects__project_id__yarns__yarn_id__delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}},{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/image":{"post":{"tags":["projects"],"summary":"Upload Project Image","description":"Upload an image for a project.","operationId":"upload_project_image_projects__project_id__image_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_project_image_projects__project_id__image_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/image/{image_id}":{"delete":{"tags":["projects"],"summary":"Delete Project Image","description":"Soft delete a project image.","operationId":"delete_project_image_projects__project_id__image__image_id__delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}},{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/image/{image_id}/set-cover":{"post":{"tags":["projects"],"summary":"Set Project Cover Image","description":"Set an image as the cover image for a project (Plus feature).","operationId":"set_project_cover_image_projects__project_id__image__image_id__set_cover_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}},{"name":"image_id","in":"path","required":true,"schema":{"type":"integer","title":"Image Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/check":{"get":{"tags":["admin"],"summary":"Check Admin Enabled","description":"Check if admin functionality is enabled for the current user.\n\nChecks if user has admin role from Auth0.","operationId":"check_admin_enabled_admin_check_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/metrics":{"get":{"tags":["admin"],"summary":"Get Metrics","description":"Return admin metrics as JSON (canvas / tooling only — not the HTML UI).\n\nGated to admin role, or loopback for the local canvas proxy.","operationId":"get_metrics_admin_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/fragment":{"get":{"tags":["admin"],"summary":"Admin Fragment","description":"Admin panel fragment with Metrics / Tools sub-tabs (loaded by the dashboard tab).\n\nDefault section is Metrics (read-only snapshot). Tools via /admin/tools.\nRequires Auth0 admin role — never shown to regular users.\n\nArgs:\n    refresh: If True, bypass users cache and fetch fresh data from Auth0.","operationId":"admin_fragment_admin_fragment_get","parameters":[{"name":"section","in":"query","required":false,"schema":{"type":"string","default":"metrics","title":"Section"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/tools":{"get":{"tags":["admin"],"summary":"Admin Tools Panel","description":"Tools sub-tab partial: cleanup and backups. Admin role required.","operationId":"admin_tools_panel_admin_tools_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/admin/stats":{"get":{"tags":["admin"],"summary":"Admin Stats Panel","description":"Metrics sub-tab partial (HTML). Admin role required.\n\nDistinct from GET /admin/metrics which returns JSON for tooling.\n\nArgs:\n    refresh: If True, bypass users cache and fetch fresh data from Auth0.","operationId":"admin_stats_panel_admin_stats_get","parameters":[{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/billing/repair":{"get":{"tags":["admin"],"summary":"Billing Repair List","description":"List Polar subscriptions that never linked to an Auth0 user (admin only).","operationId":"billing_repair_list_admin_billing_repair_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["admin"],"summary":"Billing Repair Apply","description":"Manually grant Plus + attach subscription_id for a given Auth0 sub.\n\nBody JSON:\n  user_id: Auth0 sub (required)\n  subscription_id: Polar subscription id (required)\n  polar_customer_id: optional, used to set customer.external_id on Polar\n  set_polar_external_id: bool, default true","operationId":"billing_repair_apply_admin_billing_repair_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/billing/lookup-email":{"get":{"tags":["admin"],"summary":"Billing Lookup Email","description":"Optional Auth0 email → user_id lookup for admin repair UI (never auto-applies).","operationId":"billing_lookup_email_admin_billing_lookup_email_get","parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","default":"","title":"Email"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/billing/reconciliation":{"get":{"tags":["admin"],"summary":"Billing Reconciliation List","description":"List queued credit reconciliation events for admin repair.","operationId":"billing_reconciliation_list_admin_billing_reconciliation_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":100,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/purge-claims/{item_type}/{item_id}/recover":{"post":{"tags":["admin"],"summary":"Recover Stale Purge Claim","description":"Make an interrupted storage purge retryable after all app workers are stopped.","operationId":"recover_stale_purge_claim_admin_purge_claims__item_type___item_id__recover_post","parameters":[{"name":"item_type","in":"path","required":true,"schema":{"type":"string","title":"Item Type"}},{"name":"item_id","in":"path","required":true,"schema":{"type":"integer","title":"Item Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/billing/reconciliation/{event_id}":{"post":{"tags":["admin"],"summary":"Billing Reconciliation Apply","description":"Resolve or retry a queued credit reconciliation event.","operationId":"billing_reconciliation_apply_admin_billing_reconciliation__event_id__post","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"integer","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/cleanup":{"post":{"tags":["admin"],"summary":"Run Cleanup","description":"Manually trigger cleanup of soft-deleted records.\n\nQuery params:\n    force: If true, delete ALL soft-deleted records regardless of age.\n           Default false (only delete records older than TTL).\n    include_orphans: If true, also delete orphan files (files on disk\n                    not referenced in database). Default false.\n\nReturns:\n    JSON with counts of deleted items.","operationId":"run_cleanup_admin_cleanup_post","parameters":[{"name":"force","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Force"}},{"name":"include_orphans","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Orphans"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin/storage/delete-keys":{"post":{"tags":["admin"],"summary":"Delete Storage Keys","description":"Replay manual cleanup for orphaned storage keys.","operationId":"delete_storage_keys_admin_storage_delete_keys_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/backups":{"get":{"tags":["admin"],"summary":"Get Backups","description":"List all available backups.\n\nReturns:\n    JSON with list of backups (name, size, created, reason).","operationId":"get_backups_admin_backups_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["admin"],"summary":"Create Manual Backup","description":"Create a manual backup.\n\nReturns:\n    JSON with backup info or error.","operationId":"create_manual_backup_admin_backups_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/admin/backups/rotate":{"post":{"tags":["admin"],"summary":"Rotate Old Backups","description":"Manually rotate (delete) old backups beyond retention period.\n\nReturns:\n    JSON with count of deleted backups.","operationId":"rotate_old_backups_admin_backups_rotate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/yarn/{yarn_id}/usage":{"post":{"tags":["usage"],"summary":"Finish Ball Unassigned","description":"Record finished ball(s) without assigning to a user project.\nUses the hidden system project for unassigned usage.\n\n- Validates yarn exists and has sufficient quantity\n- Creates YarnUsage record against system project\n- Decrements yarn.quantity atomically\n- Accepts optional completed_at date (YYYY-MM-DD format)","operationId":"finish_ball_unassigned_yarn__yarn_id__usage_post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_finish_ball_unassigned_yarn__yarn_id__usage_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["usage"],"summary":"Get Yarn Usage History","description":"Get usage history for a specific yarn.\nReturns a partial template showing all usage records.","operationId":"get_yarn_usage_history_yarn__yarn_id__usage_get","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/projects/{project_id}/yarns/{yarn_id}/finish":{"post":{"tags":["usage"],"summary":"Finish Ball In Project","description":"Record finished ball(s) for a specific yarn within a project.\n\n- Validates project and yarn exist\n- Validates yarn is linked to project (planning)\n- Creates YarnUsage record\n- Decrements yarn.quantity atomically\n- Accepts optional completed_at date (YYYY-MM-DD format)","operationId":"finish_ball_in_project_projects__project_id__yarns__yarn_id__finish_post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}},{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_finish_ball_in_project_projects__project_id__yarns__yarn_id__finish_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user/preferences":{"get":{"tags":["user"],"summary":"Get Preferences","description":"Get user preferences from token claims.\n\nReturns preferences from Auth0 user_metadata (added to token via Post Login Action).\nFalls back to defaults if user is not authenticated.","operationId":"get_preferences_user_preferences_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"patch":{"tags":["user"],"summary":"Update Preferences","description":"Update user preferences in Auth0 user_metadata.\n\nUpdates the user_metadata in Auth0 via Management API.\nThe updated preferences will be available in the ID token after next login.\n\nNote: Changes won't reflect in current session until re-login,\nbut localStorage serves as immediate cache.","operationId":"update_preferences_user_preferences_patch","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/user/credits":{"get":{"tags":["user"],"summary":"Get Credits","description":"Get current AI credit balance for the authenticated user.\n\nUsed by frontend to dynamically update UI when credits change.\nReturns {\"credits\": N} where N is the current balance.","operationId":"get_credits_user_credits_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/user/account":{"get":{"tags":["user"],"summary":"Account Page","description":"Account page with Overview, Preferences, and Payments tabs.\n\nRequires authentication.","operationId":"account_page_user_account_get","parameters":[{"name":"tab","in":"query","required":false,"schema":{"type":"string","default":"overview","title":"Tab"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user/account/preferences":{"post":{"tags":["user"],"summary":"Save Account Preferences","description":"Save account preferences from form submission.\n\nUpdates both preferred_tab and timezone in Auth0 user_metadata.\nReturns the preferences tab partial.","operationId":"save_account_preferences_user_account_preferences_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/user/dismiss-wrapped-banner":{"post":{"tags":["user"],"summary":"Dismiss Wrapped Banner","description":"Dismiss the wrapped season banner for a specific year.\n\nSets a cookie to remember the dismissal for this year.\nReturns empty response to swap out the banner.","operationId":"dismiss_wrapped_banner_user_dismiss_wrapped_banner_post","parameters":[{"name":"year","in":"query","required":true,"schema":{"type":"integer","title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/polar":{"post":{"tags":["billing"],"summary":"Polar Webhook","description":"Handle Polar webhook events.\n\nEvents handled:\n- order.paid: Grant AI credits based on product metadata\n- subscription.active: Set Plus plan\n- subscription.past_due: Set Plus (Grace) with 7-day window\n- subscription.canceled: Revert to Free\n- subscription.revoked: Revert to Free\n- subscription.updated: Log subscription changes","operationId":"polar_webhook_webhooks_polar_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/checkout/plus":{"get":{"tags":["billing"],"summary":"Checkout Plus","description":"Create a Polar checkout session for Plus (monthly + yearly products).\n\nCRITICAL: passes Auth0 ``sub`` as ``external_customer_id`` so\n``subscription.*`` webhooks can map payments back to the user.\nStatic Polar buy links cannot set external_id — do not use them here.","operationId":"checkout_plus_checkout_plus_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/checkout/credits/{pack}":{"get":{"tags":["billing"],"summary":"Checkout Credits","description":"Create Polar checkout session for credit pack purchase.","operationId":"checkout_credits_checkout_credits__pack__get","parameters":[{"name":"pack","in":"path","required":true,"schema":{"type":"string","title":"Pack"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/success":{"get":{"tags":["billing"],"summary":"Billing Success","description":"Post-checkout success page.\n\nThis page is UX-only. It does NOT grant entitlements.\nAll entitlements are applied via webhooks.","operationId":"billing_success_billing_success_get","parameters":[{"name":"type","in":"query","required":false,"schema":{"type":"string","default":"unknown","title":"Type"}},{"name":"pack","in":"query","required":false,"schema":{"type":"string","title":"Pack"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/billing/dismiss-grace":{"post":{"tags":["billing"],"summary":"Dismiss Grace Banner","description":"Dismiss grace period banner and downgrade to Free.\n\nCalled when user clicks \"Dismiss\" on the payment warning banner.","operationId":"dismiss_grace_banner_billing_dismiss_grace_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/billing/portal":{"get":{"tags":["billing"],"summary":"Billing Portal","description":"Redirect to Polar customer portal for payment management.","operationId":"billing_portal_billing_portal_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/wrapped/{year}":{"get":{"tags":["wrapped"],"summary":"Wrapped Year","description":"Display wrapped for a specific year.\n\nShows year-in-review with yarn usage and project statistics.\nOnly accessible to eligible users (those with activity in that year).","operationId":"wrapped_year_wrapped__year__get","parameters":[{"name":"year","in":"path","required":true,"schema":{"type":"integer","title":"Year"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/wrapped":{"get":{"tags":["wrapped"],"summary":"Wrapped Current","description":"Redirect to current wrapped year.\n\nAutomatically determines the appropriate year based on current date.","operationId":"wrapped_current_wrapped_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/import/csv":{"get":{"tags":["import"],"summary":"Import Csv Page","description":"Show the CSV import upload page.","operationId":"import_csv_page_import_csv_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/import":{"get":{"tags":["import"],"summary":"Import Landing","description":"Show the import landing page with CSV and Ravelry options.","operationId":"import_landing_import_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/import/csv/upload":{"post":{"tags":["import"],"summary":"Upload Csv","description":"Parse uploaded CSV/XLSX and return the column mapping screen.","operationId":"upload_csv_import_csv_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_csv_import_csv_upload_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/import/ravelry":{"get":{"tags":["import"],"summary":"Import Ravelry Page","description":"Show the Ravelry import page with guided instructions.","operationId":"import_ravelry_page_import_ravelry_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/import/ravelry/upload":{"post":{"tags":["import"],"summary":"Upload Ravelry","description":"Parse uploaded Ravelry export and return the column mapping screen.","operationId":"upload_ravelry_import_ravelry_upload_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_ravelry_import_ravelry_upload_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/import/csv/execute":{"post":{"tags":["import"],"summary":"Execute Csv Import","description":"Execute the import with the user-confirmed mapping.","operationId":"execute_csv_import_import_csv_execute_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/import/csv/undo":{"post":{"tags":["import"],"summary":"Undo Import","description":"Undo a recent import by soft-deleting all yarns from that batch.\n\nOnly works within IMPORT_UNDO_WINDOW_MINUTES of import execution.","operationId":"undo_import_import_csv_undo_post","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/trash":{"get":{"tags":["trash"],"summary":"Trash Page","description":"Display trash page with soft-deleted yarn and projects.\n\nShows items that are soft-deleted (is_deleted=True) and communicates\nthe 15-day retention period before permanent deletion.\n\nPagination prevents unbounded queries and storage signing requests.","operationId":"trash_page_trash_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/trash/restore/yarn/{yarn_id}":{"post":{"tags":["trash"],"summary":"Restore Yarn","description":"Restore a soft-deleted yarn (clear is_deleted and deleted_at).","operationId":"restore_yarn_trash_restore_yarn__yarn_id__post","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/trash/restore/project/{project_id}":{"post":{"tags":["trash"],"summary":"Restore Project","description":"Restore a soft-deleted project (clear is_deleted and deleted_at).","operationId":"restore_project_trash_restore_project__project_id__post","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/trash/permanent/yarn/{yarn_id}":{"delete":{"tags":["trash"],"summary":"Permanent Delete Yarn","description":"Permanently delete a yarn from trash (hard delete).","operationId":"permanent_delete_yarn_trash_permanent_yarn__yarn_id__delete","parameters":[{"name":"yarn_id","in":"path","required":true,"schema":{"type":"integer","title":"Yarn Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/trash/permanent/project/{project_id}":{"delete":{"tags":["trash"],"summary":"Permanent Delete Project","description":"Permanently delete a project from trash (hard delete).","operationId":"permanent_delete_project_trash_permanent_project__project_id__delete","parameters":[{"name":"project_id","in":"path","required":true,"schema":{"type":"integer","title":"Project Id"}}],"responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health","description":"Health check endpoint for monitoring.\n\nReturns 200 OK with basic status. No auth, no DB, no side effects.\nCreated as part of Phase 2 monitoring improvements after Aug 2026 outage.","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/":{"get":{"summary":"Landing","description":"Public landing page.\n\nAccepts HEAD as well as GET so Twitterbot/X (and other crawlers) can\nprobe the URL without downloading the body before reading OG/Twitter\ncard meta tags.","operationId":"landing__head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Landing","description":"Public landing page.\n\nAccepts HEAD as well as GET so Twitterbot/X (and other crawlers) can\nprobe the URL without downloading the body before reading OG/Twitter\ncard meta tags.","operationId":"landing__head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/dashboard":{"get":{"summary":"Dashboard","description":"Dashboard with yarn list and upload form.\n\nRequires authentication when Auth0 is configured.\nTab param overrides user preference when navigating from other pages.","operationId":"dashboard_dashboard_get","parameters":[{"name":"tab","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tab"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dashboard/overview":{"get":{"summary":"Dashboard Overview","description":"Dashboard overview page with stats, recent yarns, current projects, and inspiration.","operationId":"dashboard_overview_dashboard_overview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/dashboard/skip-onboarding":{"post":{"summary":"Skip Onboarding","description":"Skip the onboarding flow and show normal dashboard.\n\nSets a session flag so the onboarding empty state is not shown again\nduring this session. The flag resets if user logs out and back in\nwith yarn_count == 0.","operationId":"skip_onboarding_dashboard_skip_onboarding_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/pricing":{"get":{"summary":"Pricing","description":"Pricing page.","operationId":"pricing_pricing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/upgrade-prompt":{"get":{"summary":"Upgrade Prompt","description":"Return upgrade prompt partial for gated features.","operationId":"upgrade_prompt_upgrade_prompt_get","parameters":[{"name":"feature","in":"query","required":false,"schema":{"type":"string","default":"","title":"Feature"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/terms":{"get":{"summary":"Terms","description":"Terms of Use page.","operationId":"terms_terms_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/refund":{"get":{"summary":"Refund","description":"Refund Policy page.","operationId":"refund_refund_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/privacy":{"get":{"summary":"Privacy","description":"Privacy Policy page.","operationId":"privacy_privacy_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/how-to-track-your-yarn-stash":{"get":{"summary":"How To Track","description":"How to Track Your Yarn Stash - SEO content page.","operationId":"how_to_track_how_to_track_your_yarn_stash_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/ravelry-alternative-yarn-stash":{"get":{"summary":"Ravelry Alternative","description":"Ravelry Alternative for Yarn Stash Tracking - SEO content page.","operationId":"ravelry_alternative_ravelry_alternative_yarn_stash_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/robots.txt":{"get":{"summary":"Robots Txt","description":"Serve robots.txt for search engine crawlers.","operationId":"robots_txt_robots_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/sitemap.xml":{"get":{"summary":"Sitemap Xml","description":"Serve sitemap.xml for search engine indexing.","operationId":"sitemap_xml_sitemap_xml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/llms.txt":{"get":{"summary":"Llms Txt","description":"Serve llms.txt for AI assistant discoverability.","operationId":"llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/auth/error":{"get":{"summary":"Auth Error","description":"Auth0 custom error page. Receives error details via query params.","operationId":"auth_error_auth_error_get","parameters":[{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/login":{"get":{"summary":"Login","description":"Login page - redirects to Auth0 if configured.","operationId":"login_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"Body_create_project_projects__post":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"project_type":{"type":"string","title":"Project Type","default":"other"},"status":{"type":"string","title":"Status","default":"planned"},"files":{"items":{"type":"string","format":"binary"},"type":"array","title":"Files","default":[]}},"type":"object","required":["name"],"title":"Body_create_project_projects__post"},"Body_create_yarn_yarn__post":{"properties":{"files":{"items":{"type":"string","format":"binary"},"type":"array","title":"Files","default":[]},"use_ai":{"type":"boolean","title":"Use Ai","default":false},"brand":{"type":"string","title":"Brand","default":""},"name":{"type":"string","title":"Name","default":""}},"type":"object","title":"Body_create_yarn_yarn__post"},"Body_finish_ball_in_project_projects__project_id__yarns__yarn_id__finish_post":{"properties":{"quantity":{"type":"integer","title":"Quantity","default":1},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"}},"type":"object","title":"Body_finish_ball_in_project_projects__project_id__yarns__yarn_id__finish_post"},"Body_finish_ball_unassigned_yarn__yarn_id__usage_post":{"properties":{"quantity":{"type":"integer","title":"Quantity","default":1},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"}},"type":"object","title":"Body_finish_ball_unassigned_yarn__yarn_id__usage_post"},"Body_update_skein_usage_yarn__yarn_id__skein_usage_post":{"properties":{"percent_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Percent Used"},"remaining_grams":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Remaining Grams"}},"type":"object","title":"Body_update_skein_usage_yarn__yarn_id__skein_usage_post"},"Body_upload_csv_import_csv_upload_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_csv_import_csv_upload_post"},"Body_upload_project_image_projects__project_id__image_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_project_image_projects__project_id__image_post"},"Body_upload_ravelry_import_ravelry_upload_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_upload_ravelry_import_ravelry_upload_post"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}}}}