openapi: 3.1.0 info: # Do not change the title, if the title changes, the import paths will be broken title: Api version: 0.1.0 description: ToolRate API — Tool listing and rating platform servers: - url: /api description: Base API path tags: - name: health description: Health operations - name: tools description: Tool management - name: ratings description: Tool ratings - name: analytics description: Analytics and aggregated statistics - name: auth description: Authentication - name: users description: User management (admin only) - name: audit description: Audit log paths: /healthz: get: operationId: healthCheck tags: [health] summary: Health check description: Returns server health status responses: "200": description: Healthy content: application/json: schema: $ref: "#/components/schemas/HealthStatus" /version: get: operationId: getVersion tags: [health] summary: Build version information description: Returns the running build version, commit SHA and build date responses: "200": description: Version information content: application/json: schema: $ref: "#/components/schemas/VersionInfo" /tools: get: operationId: listTools tags: [tools] summary: List all tools parameters: - name: category in: query required: false schema: type: string - name: search in: query required: false schema: type: string - name: sort in: query required: false schema: type: string enum: [newest, top_rated, most_reviewed, name_asc, name_desc, recently_updated] - name: tags in: query required: false description: Comma-separated tags; tool must include all of them schema: type: string - name: features in: query required: false description: Comma-separated features; tool must include all of them schema: type: string - name: minRating in: query required: false description: Minimum average combined rating (0-5) schema: type: number minimum: 0 maximum: 5 responses: "200": description: List of tools content: application/json: schema: type: array items: $ref: "#/components/schemas/ToolWithStats" post: operationId: createTool tags: [tools] summary: Create a new tool requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ToolInput" responses: "201": description: Created tool content: application/json: schema: $ref: "#/components/schemas/Tool" "400": description: Validation error content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /compare: get: operationId: listCompareTools tags: [tools] summary: Compare tools side by side (premium) parameters: - name: ids in: query required: true schema: type: string description: Comma-separated tool ids responses: "200": description: Tools in requested order content: application/json: schema: type: array items: $ref: "#/components/schemas/ToolWithStats" "401": description: Authentication required content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Premium feature required content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /tools/{id}/rating-history: get: operationId: getToolRatingHistory tags: [tools] summary: Get a tool's rating history over time parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Rating history content: application/json: schema: type: array items: $ref: "#/components/schemas/RatingHistoryItem" "400": description: Invalid id content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /tools/{id}: get: operationId: getTool tags: [tools] summary: Get a tool by ID parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Tool details content: application/json: schema: $ref: "#/components/schemas/ToolWithStats" "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" patch: operationId: updateTool tags: [tools] summary: Update a tool parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ToolUpdate" responses: "200": description: Updated tool content: application/json: schema: $ref: "#/components/schemas/Tool" "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" delete: operationId: deleteTool tags: [tools] summary: Delete a tool parameters: - name: id in: path required: true schema: type: integer responses: "204": description: Deleted "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /tools/trash: get: operationId: listTrashedTools tags: [tools] summary: List trashed (soft-deleted) tools parameters: - name: search in: query required: false schema: type: string responses: "200": description: List of trashed tools content: application/json: schema: type: array items: $ref: "#/components/schemas/Tool" "403": description: Feature "trash" requires a higher tier content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" post: operationId: trashTools tags: [tools] summary: Move tools to trash (admin) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TrashToolsInput" responses: "200": description: Tools trashed content: application/json: schema: type: object properties: trashed: type: integer "403": description: Admin required content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" delete: operationId: deleteTrashedTools tags: [tools] summary: Permanently delete trashed tools (admin) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TrashToolsInput" responses: "204": description: Deleted "403": description: Admin required content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /tools/trash/restore: post: operationId: restoreTools tags: [tools] summary: Restore trashed tools requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/TrashToolsInput" responses: "200": description: Tools restored content: application/json: schema: type: object properties: restored: type: integer "403": description: Feature "trash" requires a higher tier content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /tools/trash/empty: post: operationId: emptyTrash tags: [tools] summary: Permanently delete all trashed tools (admin) responses: "200": description: Trash emptied content: application/json: schema: type: object properties: deleted: type: integer "403": description: Admin required content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /tools/{id}/ratings: get: operationId: listToolRatings tags: [ratings] summary: List ratings for a tool parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Ratings list content: application/json: schema: type: array items: $ref: "#/components/schemas/Rating" post: operationId: createRating tags: [ratings] summary: Submit a rating for a tool parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RatingInput" responses: "201": description: Created rating content: application/json: schema: $ref: "#/components/schemas/Rating" "400": description: Validation error content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: Tool not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /analytics/summary: get: operationId: getAnalyticsSummary tags: [analytics] summary: Overall platform statistics responses: "200": description: Platform-level summary stats content: application/json: schema: $ref: "#/components/schemas/AnalyticsSummary" /analytics/top-tools: get: operationId: getTopTools tags: [analytics] summary: Top-rated tools parameters: - name: limit in: query required: false schema: type: integer - name: metric in: query required: false schema: type: string enum: [usefulness, usability, combined] responses: "200": description: Top tools list content: application/json: schema: type: array items: $ref: "#/components/schemas/TopToolEntry" /analytics/by-category: get: operationId: getAnalyticsByCategory tags: [analytics] summary: Rating statistics grouped by category responses: "200": description: Per-category statistics content: application/json: schema: type: array items: $ref: "#/components/schemas/CategoryStats" /analytics/rating-distribution: get: operationId: getRatingDistribution tags: [analytics] summary: Distribution of rating scores across the platform parameters: - name: toolId in: query required: false schema: type: integer responses: "200": description: Rating score distribution content: application/json: schema: $ref: "#/components/schemas/RatingDistribution" /categories: get: operationId: listCategories tags: [tools] summary: List all distinct tool categories responses: "200": description: Categories list content: application/json: schema: type: array items: type: string /features/all: get: operationId: listAllFeatures tags: [tools] summary: List all distinct feature strings across all tools responses: "200": description: All known features content: application/json: schema: type: array items: type: string /tags/all: get: operationId: listAllTags tags: [tools] summary: List all distinct tag strings across all tools responses: "200": description: All known tags content: application/json: schema: type: array items: type: string /auth/mode: get: operationId: getAuthMode tags: [auth] summary: Get authentication mode (oidc or local) responses: "200": description: Auth mode content: application/json: schema: $ref: "#/components/schemas/AuthMode" /auth/csrf: get: operationId: getCsrfToken tags: [auth] summary: Get a CSRF token for state-changing requests responses: "200": description: CSRF token content: application/json: schema: $ref: "#/components/schemas/CsrfToken" /auth/login: post: operationId: localLogin tags: [auth] summary: Local username/password login requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LocalLoginInput" responses: "200": description: Logged in successfully content: application/json: schema: $ref: "#/components/schemas/AuthUser" "401": description: Invalid credentials content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /auth/me: get: operationId: getMe tags: [auth] summary: Get current authenticated user responses: "200": description: Current user info content: application/json: schema: $ref: "#/components/schemas/AuthUser" "401": description: Not authenticated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /auth/me/password: post: operationId: changeMyPassword tags: [auth] summary: Change own password (local users only) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ChangePasswordInput" responses: "204": description: Password changed "400": description: Invalid input or wrong current password content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "401": description: Not authenticated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "422": description: OIDC user - password is managed by the identity provider content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many attempts content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /auth/password-redirect: get: operationId: getPasswordRedirect tags: [auth] summary: Get redirect URL for managing credentials in the identity provider responses: "200": description: Redirect URL (null in local mode) content: application/json: schema: $ref: "#/components/schemas/PasswordRedirect" /auth/me/preferences: get: operationId: getMePreferences tags: [auth] summary: Get current user's browse preferences responses: "200": description: User preferences content: application/json: schema: $ref: "#/components/schemas/UserPreferences" "401": description: Not authenticated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" put: operationId: updateMePreferences tags: [auth] summary: Update current user's browse preferences requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UserPreferences" responses: "200": description: Updated preferences content: application/json: schema: $ref: "#/components/schemas/UserPreferences" "401": description: Not authenticated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /auth/me/watchlist: get: operationId: getMeWatchlist tags: [auth] summary: Get current user's watchlist tools (premium) responses: "200": description: Watchlist tools in saved order content: application/json: schema: type: array items: $ref: "#/components/schemas/ToolWithStats" "401": description: Not authenticated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Premium feature required content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /users: get: operationId: listUsers tags: [users] summary: List all local users (admin only) responses: "200": description: User list content: application/json: schema: type: array items: $ref: "#/components/schemas/User" "401": description: Not authenticated content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "403": description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" post: operationId: createUser tags: [users] summary: Create a new local user (admin only) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UserCreateInput" responses: "201": description: Created user content: application/json: schema: $ref: "#/components/schemas/User" "400": description: Validation error content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "409": description: Username already exists content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /users/{id}: patch: operationId: updateUser tags: [users] summary: Update user role (admin only) parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UserRoleUpdate" responses: "200": description: Updated user content: application/json: schema: $ref: "#/components/schemas/User" "404": description: User not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" delete: operationId: deleteUser tags: [users] summary: Delete a user (admin only) parameters: - name: id in: path required: true schema: type: integer responses: "204": description: Deleted /users/{id}/password: patch: operationId: setUserPassword tags: [users] summary: Set/reset a user's password (admin only, local users only) parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SetPasswordInput" responses: "204": description: Password updated "400": description: Validation error content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "404": description: User not found content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "422": description: OIDC user - password is managed by the identity provider content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" "429": description: Too many attempts content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /audit-logs: get: operationId: listAuditLogs tags: [audit] summary: List audit log entries (admin only) parameters: - name: entityType in: query required: false schema: type: string - name: entityId in: query required: false schema: type: integer - name: limit in: query required: false schema: type: integer responses: "200": description: Audit log entries content: application/json: schema: type: array items: $ref: "#/components/schemas/AuditLog" components: schemas: HealthStatus: type: object properties: status: type: string required: - status VersionInfo: type: object required: [version] properties: version: type: string commitSha: type: ["string", "null"] buildDate: type: ["string", "null"] trashRetentionDays: type: integer AuthMode: type: object required: [mode] properties: mode: type: string enum: [oidc, local] CsrfToken: type: object required: [token] properties: token: type: string LocalLoginInput: type: object required: [username, password] properties: username: type: string password: type: string User: type: object required: [id, username, role, createdAt] properties: id: type: integer username: type: string email: type: ["string", "null"] role: type: string enum: [admin, user] tier: type: string enum: [free, premium, enterprise] authProvider: type: string enum: [local, oidc] default: local createdAt: type: string format: date-time UserCreateInput: type: object required: [username, password] properties: username: type: string minLength: 2 password: type: string minLength: 6 email: type: string role: type: string enum: [admin, user] tier: type: string enum: [free, premium, enterprise] UserRoleUpdate: type: object properties: role: type: string enum: [admin, user] tier: type: string enum: [free, premium, enterprise] ChangePasswordInput: type: object required: [currentPassword, newPassword] properties: currentPassword: type: string minLength: 1 newPassword: type: string minLength: 6 SetPasswordInput: type: object required: [password] properties: password: type: string minLength: 6 PasswordRedirect: type: object required: [url] properties: url: type: ["string", "null"] AuditLog: type: object required: [id, entityType, action, userId, username, createdAt] properties: id: type: integer entityType: type: string entityId: type: ["integer", "null"] action: type: string userId: type: string username: type: string changes: type: ["string", "null"] createdAt: type: string format: date-time Tool: type: object required: [id, name, description, category, createdAt, updatedAt] properties: id: type: integer name: type: string description: type: string category: type: string websiteUrl: type: ["string", "null"] iconUrl: type: ["string", "null"] createdBy: type: ["string", "null"] features: type: array items: type: string tags: type: array items: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time deletedAt: type: ["string", "null"] format: date-time deletedBy: type: ["string", "null"] RatingHistoryItem: type: object required: [date, usefulness, usability, combined] properties: date: type: string format: date-time usefulness: type: number usability: type: number combined: type: number ToolWithStats: type: object required: [id, name, description, category, createdAt, updatedAt, ratingCount, avgUsefulness, avgUsability, avgCombined] properties: id: type: integer name: type: string description: type: string category: type: string websiteUrl: type: ["string", "null"] iconUrl: type: ["string", "null"] createdBy: type: ["string", "null"] features: type: array items: type: string tags: type: array items: type: string createdAt: type: string format: date-time updatedAt: type: string format: date-time ratingCount: type: integer avgUsefulness: type: ["number", "null"] avgUsability: type: ["number", "null"] avgCombined: type: ["number", "null"] ToolInput: type: object required: [name, description, category] properties: name: type: string minLength: 1 description: type: string minLength: 1 category: type: string minLength: 1 websiteUrl: type: string iconUrl: type: string features: type: array items: type: string tags: type: array items: type: string ToolUpdate: type: object properties: name: type: string minLength: 1 description: type: string category: type: string websiteUrl: type: ["string", "null"] iconUrl: type: ["string", "null"] features: type: array items: type: string tags: type: array items: type: string TrashToolsInput: type: object required: [ids] properties: ids: type: array minItems: 1 maxItems: 500 items: type: integer Rating: type: object required: [id, toolId, usefulness, usability, createdAt] properties: id: type: integer toolId: type: integer usefulness: type: integer minimum: 1 maximum: 5 usability: type: integer minimum: 1 maximum: 5 comment: type: ["string", "null"] reviewerName: type: ["string", "null"] createdAt: type: string format: date-time RatingInput: type: object required: [usefulness, usability] properties: usefulness: type: integer minimum: 1 maximum: 5 usability: type: integer minimum: 1 maximum: 5 comment: type: string reviewerName: type: string AnalyticsSummary: type: object required: [totalTools, totalRatings, avgUsefulness, avgUsability, avgCombined, categoriesCount] properties: totalTools: type: integer totalRatings: type: integer avgUsefulness: type: ["number", "null"] avgUsability: type: ["number", "null"] avgCombined: type: ["number", "null"] categoriesCount: type: integer mostRatedTool: $ref: "#/components/schemas/ToolWithStats" TopToolEntry: type: object required: [tool, score, ratingCount] properties: tool: $ref: "#/components/schemas/ToolWithStats" score: type: number ratingCount: type: integer CategoryStats: type: object required: [category, toolCount, totalRatings, avgUsefulness, avgUsability] properties: category: type: string toolCount: type: integer totalRatings: type: integer avgUsefulness: type: ["number", "null"] avgUsability: type: ["number", "null"] RatingDistribution: type: object required: [usefulness, usability] properties: usefulness: type: array items: $ref: "#/components/schemas/ScoreBucket" usability: type: array items: $ref: "#/components/schemas/ScoreBucket" ScoreBucket: type: object required: [score, count] properties: score: type: integer count: type: integer AuthUser: type: object required: [sub] properties: sub: type: string email: type: ["string", "null"] name: type: ["string", "null"] preferredUsername: type: ["string", "null"] role: type: string enum: [admin, user] tier: type: string enum: [free, premium, enterprise] entitlements: type: array items: type: string isLocal: type: boolean UserPreferences: type: object properties: view: type: string enum: [grid, table, rows] density: type: string enum: [cozy, compact] watchlist: type: array items: type: integer ErrorResponse: type: object required: [error] properties: error: type: string