diff --git a/.opencode/changelog/2026-07-06.md b/.opencode/changelog/2026-07-06.md new file mode 100644 index 000000000..ac06c06bd --- /dev/null +++ b/.opencode/changelog/2026-07-06.md @@ -0,0 +1,9 @@ +# Changes - 2026-07-06 + +## Expose Board Automation APIs +- **Why**: Board management automation needed broader OpenAPI and MCP coverage, including board settings and global board defaults. +- **What**: Exposed board duplicate, rename, visibility, home-board, mobile-home-board, and partial settings procedures to OpenAPI/MCP; added typed global board settings get/update procedures; bumped OpenAPI schema version to `1.1.0`; regenerated the docs OpenAPI schema; documented board automation use cases. +- **Files**: `packages/api/src/router/board.ts`, `packages/api/src/router/serverSettings.ts`, `packages/api/src/open-api.ts`, `packages/api/src/mcp.ts`, `apps/docs/static/api/open-api-schema.json`, `apps/docs/docs/management/api/index.mdx` +- **Tests**: Added/updated OpenAPI, MCP, and server settings tests. Verified targeted Vitest tests, `@homarr/api` typecheck/lint, and `@homarr/docs` typecheck/lint. +- **Docs**: Updated API management docs and regenerated the static OpenAPI schema. +- **Risks**: Existing lint warnings remain outside the new code paths; full docs build was not run because the local filesystem hit `ENOSPC` during Next/Turbopack dev output generation. diff --git a/apps/docs/docs/management/api/index.mdx b/apps/docs/docs/management/api/index.mdx index 177ec8c9c..2c5bd42e2 100644 --- a/apps/docs/docs/management/api/index.mdx +++ b/apps/docs/docs/management/api/index.mdx @@ -70,4 +70,16 @@ API key authentication enables programmatic access for: - Automation scripts and CI/CD pipelines - Third-party integrations - Custom dashboards and monitoring tools -- Container orchestration systems that need to sync app definitions \ No newline at end of file +- Container orchestration systems that need to sync app definitions + +## Board Automation + +The OpenAPI and MCP APIs include board management endpoints for common automation workflows: + +- Create, duplicate, rename and delete boards +- Change board visibility +- Update board visual and behavior settings +- Set desktop and mobile home boards for the current user +- Configure global board defaults such as public home boards and status behavior + +Use the interactive API reference to inspect the exact request body for each endpoint under the `boards` and `settings` tags. diff --git a/apps/docs/static/api/open-api-schema.json b/apps/docs/static/api/open-api-schema.json index 50384fba1..ca2f93d64 100644 --- a/apps/docs/static/api/open-api-schema.json +++ b/apps/docs/static/api/open-api-schema.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"Homarr API documentation","version":"1.0.0"},"servers":[{"url":"http://localhost:3000"}],"externalDocs":{"url":"https://homarr.dev"},"paths":{"/api/apps/paginated":{"get":{"operationId":"appRouter-getPaginated","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"pageSize","schema":{"default":10,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},{"in":"query","name":"page","schema":{"default":1,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}},"totalCount":{"type":"number"}},"required":["items","totalCount"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps":{"get":{"operationId":"appRouter-all","tags":["apps"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"post":{"operationId":"appRouter-create","tags":["apps"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}]},"iconUrl":{"type":"string","minLength":1},"href":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^(?!javascript)[a-zA-Z]*:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]},"pingUrl":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^https?:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]}},"required":["name","description","iconUrl","href","pingUrl"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"appId":{"type":"string"}},"required":["appId"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps/search":{"get":{"operationId":"appRouter-search","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"query","name":"query","schema":{"type":"string"},"required":true},{"in":"query","name":"limit","schema":{"default":10,"type":"number","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps/selectable":{"get":{"operationId":"appRouter-selectable","tags":["apps"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","iconUrl","href","pingUrl","description"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps/{id}":{"get":{"operationId":"appRouter-byId","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"patch":{"operationId":"appRouter-update","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}]},"iconUrl":{"type":"string","minLength":1},"href":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^(?!javascript)[a-zA-Z]*:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]},"pingUrl":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^https?:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]}},"required":["name","description","iconUrl","href","pingUrl"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"delete":{"operationId":"appRouter-delete","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/info":{"get":{"operationId":"infoRouter-getInfo","tags":["info"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"}},"required":["version"],"additionalProperties":false}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/invites":{"get":{"operationId":"inviteRouter-getAll","tags":["invites"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"expirationDate":{"type":"string"},"creator":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"id":{"type":"string"}},"required":["name","id"],"additionalProperties":false}},"required":["id","expirationDate","creator"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"post":{"operationId":"inviteRouter-createInvite","tags":["invites"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expirationDate":{"type":"string"}},"required":["expirationDate"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"token":{"type":"string"}},"required":["id","token"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/invites/{id}":{"delete":{"operationId":"inviteRouter-deleteInvite","tags":["invites"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"not":{}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users":{"post":{"operationId":"userRouter-create","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","minLength":3,"maxLength":255},"password":{"type":"string","minLength":8,"maxLength":255},"confirmPassword":{"type":"string"},"email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","minLength":0,"maxLength":0}]},"groupIds":{"type":"array","items":{"type":"string"}}},"required":["username","password","confirmPassword","groupIds"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"get":{"operationId":"userRouter-getAll","tags":["users"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"emailVerified":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","email","emailVerified","image"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/profileImage":{"put":{"operationId":"userRouter-setProfileImage","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string"},"image":{"anyOf":[{"type":"string","maxLength":350000,"pattern":"^data:image\\/(png|jpeg|gif|webp);base64,[A-Za-z0-9/+]+=*$"},{"type":"null"}]}},"required":["userId","image"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/selectable":{"get":{"operationId":"userRouter-selectable","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"query","name":"excludeExternalProviders","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","image","email"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/search":{"post":{"operationId":"userRouter-search","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"limit":{"default":10,"type":"number","minimum":1,"maximum":100}},"required":["query"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","image","email"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/{userId}":{"get":{"operationId":"userRouter-getById","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"userId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"emailVerified":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"provider":{"type":"string"},"homeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"mobileHomeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"firstDayOfWeek":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pingIconsEnabled":{"type":"boolean"},"defaultSearchEngineId":{"anyOf":[{"type":"string"},{"type":"null"}]},"openSearchInNewTab":{"type":"boolean"},"ddgBangs":{"type":"boolean"},"completedManageTour":{"type":"boolean"},"completedBoardTour":{"type":"boolean"}},"required":["id","name","email","emailVerified","image","provider","homeBoardId","mobileHomeBoardId","firstDayOfWeek","pingIconsEnabled","defaultSearchEngineId","openSearchInNewTab","ddgBangs","completedManageTour","completedBoardTour"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"delete":{"operationId":"userRouter-delete","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"userId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/profile":{"put":{"operationId":"userRouter-editProfile","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","minLength":3,"maxLength":255},"email":{"anyOf":[{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","const":""}]},{"type":"null"}]}},"required":["id","name"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/{userId}/changePassword":{"patch":{"operationId":"userRouter-changePassword","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"userId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"previousPassword":{"type":"string","minLength":1},"password":{"type":"string","minLength":8,"maxLength":255},"confirmPassword":{"type":"string"}},"required":["previousPassword","password","confirmPassword"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/changeHome":{"patch":{"operationId":"userRouter-changeHomeBoards","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"homeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"mobileHomeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"userId":{"type":"string"}},"required":["homeBoardId","mobileHomeBoardId","userId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/changeSearchEngine":{"patch":{"operationId":"userRouter-changeDefaultSearchEngine","tags":["users"],"security":[{"apikey":[]}],"deprecated":true,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"defaultSearchEngineId":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"ddgBangsEnabled":{"type":"boolean"},"userId":{"type":"string"}},"required":["defaultSearchEngineId","ddgBangsEnabled","userId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/search-preferences":{"patch":{"operationId":"userRouter-changeSearchPreferences","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"defaultSearchEngineId":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"openInNewTab":{"type":"boolean"},"ddgBangsEnabled":{"type":"boolean"},"userId":{"type":"string"}},"required":["defaultSearchEngineId","openInNewTab","ddgBangsEnabled","userId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/changeScheme":{"patch":{"operationId":"userRouter-changeColorScheme","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"colorScheme":{"type":"string","enum":["auto","light","dark"]}},"required":["colorScheme"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/ddg-bangs":{"patch":{"operationId":"userRouter-changeDdgBangs","tags":["users"],"security":[{"apikey":[]}],"deprecated":true,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ddgBangs":{"type":"boolean"},"id":{"type":"string"}},"required":["ddgBangs","id"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/firstDayOfWeek":{"patch":{"operationId":"userRouter-changeFirstDayOfWeek","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstDayOfWeek":{"type":"number","minimum":0,"maximum":6},"id":{"type":"string"}},"required":["firstDayOfWeek","id"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}},"components":{"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Invalid input data"},"code":{"type":"string","description":"The error code","example":"BAD_REQUEST"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information","example":{"code":"BAD_REQUEST","message":"Invalid input data","issues":[]}},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Authorization not provided"},"code":{"type":"string","description":"The error code","example":"UNAUTHORIZED"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information","example":{"code":"UNAUTHORIZED","message":"Authorization not provided","issues":[]}},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Insufficient access"},"code":{"type":"string","description":"The error code","example":"FORBIDDEN"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information","example":{"code":"FORBIDDEN","message":"Insufficient access","issues":[]}},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Not found"},"code":{"type":"string","description":"The error code","example":"NOT_FOUND"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information","example":{"code":"NOT_FOUND","message":"Not found","issues":[]}},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Internal server error"},"code":{"type":"string","description":"The error code","example":"INTERNAL_SERVER_ERROR"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information","example":{"code":"INTERNAL_SERVER_ERROR","message":"Internal server error","issues":[]}}},"securitySchemes":{"apikey":{"type":"apiKey","name":"ApiKey","description":"API key which can be obtained in the Homarr administration dashboard","in":"header"}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"Homarr API documentation","version":"1.1.0"},"servers":[{"url":"http://localhost:3000"}],"externalDocs":{"url":"https://homarr.dev"},"paths":{"/api/apps/paginated":{"get":{"operationId":"appRouter-getPaginated","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"pageSize","schema":{"default":10,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},{"in":"query","name":"page","schema":{"default":1,"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}},"totalCount":{"type":"number"}},"required":["items","totalCount"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps":{"get":{"operationId":"appRouter-all","tags":["apps"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"post":{"operationId":"appRouter-create","tags":["apps"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}]},"iconUrl":{"type":"string","minLength":1},"href":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^(?!javascript)[a-zA-Z]*:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]},"pingUrl":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^https?:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]}},"required":["name","description","iconUrl","href","pingUrl"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"allOf":[{"type":"object","properties":{"appId":{"type":"string"}},"required":["appId"],"additionalProperties":false},{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}]}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps/search":{"get":{"operationId":"appRouter-search","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"query","name":"query","schema":{"type":"string"},"required":true},{"in":"query","name":"limit","schema":{"default":10,"type":"number","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps/selectable":{"get":{"operationId":"appRouter-selectable","tags":["apps"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","iconUrl","href","pingUrl","description"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/apps/{id}":{"get":{"operationId":"appRouter-byId","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"iconUrl":{"type":"string"},"href":{"anyOf":[{"type":"string"},{"type":"null"}]},"pingUrl":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","description","iconUrl","href","pingUrl"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"patch":{"operationId":"appRouter-update","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":64},"description":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}]},"iconUrl":{"type":"string","minLength":1},"href":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^(?!javascript)[a-zA-Z]*:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]},"pingUrl":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^https?:\\/\\/"},{"type":"string","const":""}]},{"type":"null"}]}},"required":["name","description","iconUrl","href","pingUrl"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"delete":{"operationId":"appRouter-delete","tags":["apps"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards":{"get":{"operationId":"boardRouter-getAllBoards","tags":["boards"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"logoImageUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"isPublic":{"type":"boolean"},"creator":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","image","email"],"additionalProperties":false},{"type":"null"}]},"isHome":{"type":"boolean"},"isMobileHome":{"type":"boolean"},"userPermissions":{"type":"array","items":{"type":"object","properties":{"permission":{"type":"string","enum":["view","modify","full"]}},"required":["permission"],"additionalProperties":false}},"groupPermissions":{"type":"array","items":{"type":"object","properties":{"permission":{"type":"string","enum":["view","modify","full"]}},"required":["permission"],"additionalProperties":false}}},"required":["id","name","logoImageUrl","isPublic","creator","isHome","isMobileHome","userPermissions","groupPermissions"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"post":{"operationId":"boardRouter-createBoard","tags":["boards"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9-_]*$"},"columnCount":{"type":"number","minimum":1,"maximum":24},"isPublic":{"type":"boolean"}},"required":["name","columnCount","isPublic"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"boardId":{"type":"string"}},"required":["boardId"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/{id}/duplicate":{"post":{"operationId":"boardRouter-duplicateBoard","tags":["boards"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9-_]*$"}},"required":["name"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"boardId":{"type":"string"}},"required":["boardId"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/{id}/name":{"patch":{"operationId":"boardRouter-renameBoard","tags":["boards"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Za-z0-9-_]*$"}},"required":["name"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/{id}/visibility":{"patch":{"operationId":"boardRouter-changeBoardVisibility","tags":["boards"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"visibility":{"type":"string","enum":["public","private"]}},"required":["visibility"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/{id}":{"delete":{"operationId":"boardRouter-deleteBoard","tags":["boards"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/{id}/home":{"patch":{"operationId":"boardRouter-setHomeBoard","tags":["boards"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/{id}/mobile-home":{"patch":{"operationId":"boardRouter-setMobileHomeBoard","tags":["boards"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/{id}/settings":{"patch":{"operationId":"boardRouter-savePartialBoardSettings","tags":["boards"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pageTitle":{"anyOf":[{"type":"string"},{"type":"null"}]},"metaTitle":{"anyOf":[{"type":"string"},{"type":"null"}]},"logoImageUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"faviconImageUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"backgroundImageUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"backgroundImageAttachment":{"type":"string","enum":["fixed","scroll"]},"backgroundImageRepeat":{"type":"string","enum":["repeat","repeat-x","repeat-y","no-repeat"]},"backgroundImageSize":{"type":"string","enum":["cover","contain"]},"primaryColor":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"secondaryColor":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},"opacity":{"type":"number","minimum":0,"maximum":100},"customCss":{"type":"string","maxLength":16384},"iconColor":{"anyOf":[{"anyOf":[{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$"},{"type":"string","const":""}]},{"type":"null"}]},"itemRadius":{"anyOf":[{"type":"string","const":"xs"},{"type":"string","const":"sm"},{"type":"string","const":"md"},{"type":"string","const":"lg"},{"type":"string","const":"xl"}]},"disableStatus":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/boards/items":{"post":{"operationId":"boardRouter-addItem","tags":["boards"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"boardId":{"type":"string"},"kind":{"type":"string","enum":["clock","weather","app","iframe","video","notebook","anchorNote","dnsHoleSummary","dnsHoleControls","smartHome-entityState","smartHome-executeAutomation","stockPrice","mediaServer","calendar","downloads","mediaRequests-requestList","mediaRequests-requestStats","mediaTranscoding","minecraftServerStatus","networkControllerSummary","networkControllerStatus","rssFeed","bookmarks","indexerManager","healthMonitoring","releases","mediaReleases","dockerContainers","firewall","notifications","systemResources","coolify","systemDisks","timetable","immich-serverStats","immich-albumCarousel","paperlessNgx","tracearr","speedtestTracker","uptimeKuma","audioStats","umami","vpn","archiveTeamWarrior","ups","beszelSystemTable","beszelSystemGrid","beszelAlerts","beszelSystemStats","customApi"]},"options":{"default":{},"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"integrationIds":{"default":[],"type":"array","items":{"type":"string"}}},"required":["boardId","kind"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"itemId":{"type":"string"}},"required":["itemId"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/info":{"get":{"operationId":"infoRouter-getInfo","tags":["info"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"}},"required":["version"],"additionalProperties":false}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/invites":{"get":{"operationId":"inviteRouter-getAll","tags":["invites"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"expirationDate":{"type":"string"},"creator":{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"id":{"type":"string"}},"required":["name","id"],"additionalProperties":false}},"required":["id","expirationDate","creator"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"post":{"operationId":"inviteRouter-createInvite","tags":["invites"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"expirationDate":{"type":"string"}},"required":["expirationDate"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"token":{"type":"string"}},"required":["id","token"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/invites/{id}":{"delete":{"operationId":"inviteRouter-deleteInvite","tags":["invites"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"id","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"not":{}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/settings/board":{"get":{"operationId":"serverSettingsRouter-getBoardSettings","tags":["settings"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"homeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"mobileHomeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"enableStatusByDefault":{"type":"boolean"},"forceDisableStatus":{"type":"boolean"}},"required":["homeBoardId","mobileHomeBoardId","enableStatusByDefault","forceDisableStatus"],"additionalProperties":false}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"patch":{"operationId":"serverSettingsRouter-updateBoardSettings","tags":["settings"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"homeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"mobileHomeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"enableStatusByDefault":{"type":"boolean"},"forceDisableStatus":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"homeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"mobileHomeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"enableStatusByDefault":{"type":"boolean"},"forceDisableStatus":{"type":"boolean"}},"required":["homeBoardId","mobileHomeBoardId","enableStatusByDefault","forceDisableStatus"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users":{"post":{"operationId":"userRouter-create","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"username":{"type":"string","minLength":3,"maxLength":255},"password":{"type":"string","minLength":8,"maxLength":255},"confirmPassword":{"type":"string"},"email":{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","minLength":0,"maxLength":0}]},"groupIds":{"type":"array","items":{"type":"string"}}},"required":["username","password","confirmPassword","groupIds"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"get":{"operationId":"userRouter-getAll","tags":["users"],"security":[{"apikey":[]}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"emailVerified":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","email","emailVerified","image"],"additionalProperties":false}}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/profileImage":{"put":{"operationId":"userRouter-setProfileImage","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"userId":{"type":"string"},"image":{"anyOf":[{"type":"string","maxLength":350000,"pattern":"^data:image\\/(png|jpeg|gif|webp);base64,[A-Za-z0-9/+]+=*$"},{"type":"null"}]}},"required":["userId","image"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/selectable":{"get":{"operationId":"userRouter-selectable","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"query","name":"providers","schema":{"type":"array","items":{"type":"string","enum":["credentials","oidc","ldap"]}}}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","image","email"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/search":{"post":{"operationId":"userRouter-search","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"limit":{"default":10,"type":"number","minimum":1,"maximum":100}},"required":["query"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","image","email"],"additionalProperties":false}}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/{userId}":{"get":{"operationId":"userRouter-getById","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"userId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"email":{"anyOf":[{"type":"string"},{"type":"null"}]},"emailVerified":{"anyOf":[{"type":"string"},{"type":"null"}]},"image":{"anyOf":[{"type":"string"},{"type":"null"}]},"provider":{"type":"string"},"homeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"mobileHomeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"firstDayOfWeek":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"pingIconsEnabled":{"type":"boolean"},"defaultSearchEngineId":{"anyOf":[{"type":"string"},{"type":"null"}]},"openSearchInNewTab":{"type":"boolean"},"ddgBangs":{"type":"boolean"},"completedManageTour":{"type":"boolean"},"completedBoardTour":{"type":"boolean"}},"required":["id","name","email","emailVerified","image","provider","homeBoardId","mobileHomeBoardId","firstDayOfWeek","pingIconsEnabled","defaultSearchEngineId","openSearchInNewTab","ddgBangs","completedManageTour","completedBoardTour"],"additionalProperties":false}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}},"delete":{"operationId":"userRouter-delete","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"userId","schema":{"type":"string"},"required":true}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/profile":{"put":{"operationId":"userRouter-editProfile","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","minLength":3,"maxLength":255},"email":{"anyOf":[{"anyOf":[{"type":"string","format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},{"type":"string","const":""}]},{"type":"null"}]}},"required":["id","name"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/{userId}/changePassword":{"patch":{"operationId":"userRouter-changePassword","tags":["users"],"security":[{"apikey":[]}],"parameters":[{"in":"path","name":"userId","schema":{"type":"string"},"required":true}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"previousPassword":{"type":"string","minLength":1},"password":{"type":"string","minLength":8,"maxLength":255},"confirmPassword":{"type":"string"}},"required":["previousPassword","password","confirmPassword"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/changeHome":{"patch":{"operationId":"userRouter-changeHomeBoards","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"homeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"mobileHomeBoardId":{"anyOf":[{"type":"string"},{"type":"null"}]},"userId":{"type":"string"}},"required":["homeBoardId","mobileHomeBoardId","userId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/changeSearchEngine":{"patch":{"operationId":"userRouter-changeDefaultSearchEngine","tags":["users"],"security":[{"apikey":[]}],"deprecated":true,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"defaultSearchEngineId":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"ddgBangsEnabled":{"type":"boolean"},"userId":{"type":"string"}},"required":["defaultSearchEngineId","ddgBangsEnabled","userId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/search-preferences":{"patch":{"operationId":"userRouter-changeSearchPreferences","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"defaultSearchEngineId":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}]},"openInNewTab":{"type":"boolean"},"ddgBangsEnabled":{"type":"boolean"},"userId":{"type":"string"}},"required":["defaultSearchEngineId","openInNewTab","ddgBangsEnabled","userId"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/changeScheme":{"patch":{"operationId":"userRouter-changeColorScheme","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"colorScheme":{"type":"string","enum":["auto","light","dark"]}},"required":["colorScheme"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/ddg-bangs":{"patch":{"operationId":"userRouter-changeDdgBangs","tags":["users"],"security":[{"apikey":[]}],"deprecated":true,"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ddgBangs":{"type":"boolean"},"id":{"type":"string"}},"required":["ddgBangs","id"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}},"/api/users/firstDayOfWeek":{"patch":{"operationId":"userRouter-changeFirstDayOfWeek","tags":["users"],"security":[{"apikey":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstDayOfWeek":{"type":"number","minimum":0,"maximum":6},"id":{"type":"string"}},"required":["firstDayOfWeek","id"]}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{}}}},"400":{"description":"Invalid input data","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.BAD_REQUEST"}}}},"401":{"description":"Authorization not provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.UNAUTHORIZED"}}}},"403":{"description":"Insufficient access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.FORBIDDEN"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.NOT_FOUND"}}}},"500":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/error.INTERNAL_SERVER_ERROR"}}}}}}}},"components":{"schemas":{"error.BAD_REQUEST":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Invalid input data"},"code":{"type":"string","description":"The error code","example":"BAD_REQUEST"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Invalid input data error (400)","description":"The error information","example":{"code":"BAD_REQUEST","message":"Invalid input data","issues":[]}},"error.UNAUTHORIZED":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Authorization not provided"},"code":{"type":"string","description":"The error code","example":"UNAUTHORIZED"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Authorization not provided error (401)","description":"The error information","example":{"code":"UNAUTHORIZED","message":"Authorization not provided","issues":[]}},"error.FORBIDDEN":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Insufficient access"},"code":{"type":"string","description":"The error code","example":"FORBIDDEN"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Insufficient access error (403)","description":"The error information","example":{"code":"FORBIDDEN","message":"Insufficient access","issues":[]}},"error.NOT_FOUND":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Not found"},"code":{"type":"string","description":"The error code","example":"NOT_FOUND"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Not found error (404)","description":"The error information","example":{"code":"NOT_FOUND","message":"Not found","issues":[]}},"error.INTERNAL_SERVER_ERROR":{"type":"object","properties":{"message":{"type":"string","description":"The error message","example":"Internal server error"},"code":{"type":"string","description":"The error code","example":"INTERNAL_SERVER_ERROR"},"issues":{"description":"An array of issues that were responsible for the error","example":[],"type":"array","items":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"],"additionalProperties":false}}},"required":["message","code"],"additionalProperties":false,"title":"Internal server error error (500)","description":"The error information","example":{"code":"INTERNAL_SERVER_ERROR","message":"Internal server error","issues":[]}}},"securitySchemes":{"apikey":{"type":"apiKey","name":"ApiKey","description":"API key which can be obtained in the Homarr administration dashboard","in":"header"}}}} \ No newline at end of file diff --git a/packages/api/src/mcp.ts b/packages/api/src/mcp.ts index b39e4a97d..faf7a62be 100644 --- a/packages/api/src/mcp.ts +++ b/packages/api/src/mcp.ts @@ -9,6 +9,7 @@ import { iconsRouter } from "./router/icons"; import { infoRouter } from "./router/info"; import { integrationRouter } from "./router/integration/integration-router"; import { inviteRouter } from "./router/invite"; +import { serverSettingsRouter } from "./router/serverSettings"; import { beszelRouter } from "./router/widgets/beszel"; import { calendarRouter } from "./router/widgets/calendar"; import { dnsHoleRouter } from "./router/widgets/dns-hole"; @@ -28,6 +29,7 @@ export const mcpRouter = createTRPCRouter({ info: infoRouter, integration: integrationRouter, invite: inviteRouter, + serverSettings: serverSettingsRouter, beszel: beszelRouter, calendar: calendarRouter, dnsHole: dnsHoleRouter, diff --git a/packages/api/src/open-api.ts b/packages/api/src/open-api.ts index 5c75cfbc2..05ba467fb 100644 --- a/packages/api/src/open-api.ts +++ b/packages/api/src/open-api.ts @@ -6,6 +6,7 @@ import { appRouter } from "./router/app"; import { boardRouter } from "./router/board"; import { infoRouter } from "./router/info"; import { inviteRouter } from "./router/invite"; +import { serverSettingsRouter } from "./router/serverSettings"; import { userRouter } from "./router/user"; import { createTRPCRouter } from "./trpc"; @@ -14,13 +15,14 @@ export const openApiRouter = createTRPCRouter({ boardRouter, infoRouter, inviteRouter, + serverSettingsRouter, userRouter, }); export const openApiDocument = (base: string) => generateOpenApiDocument(openApiRouter, { title: "Homarr API documentation", - version: "1.0.0", + version: "1.1.0", baseUrl: base, docsUrl: "https://homarr.dev", securitySchemes: { diff --git a/packages/api/src/router/board.ts b/packages/api/src/router/board.ts index fe8c0dcb9..0f529a929 100644 --- a/packages/api/src/router/board.ts +++ b/packages/api/src/router/board.ts @@ -330,7 +330,16 @@ export const boardRouter = createTRPCRouter({ }), duplicateBoard: permissionRequiredProcedure .requiresPermission("board-create") + .meta({ + openapi: { method: "POST", path: "/api/boards/{id}/duplicate", tags: ["boards"], protect: true }, + mcp: { + enabled: true, + description: + "Duplicate an existing board into a new board. Requires board-create permission and view permission on the source board. REQUIRED: id (source board ID), name (unique name for the new board). Returns { boardId }", + }, + }) .input(boardDuplicateSchema) + .output(z.object({ boardId: z.string() })) .mutation(async ({ ctx, input }) => { await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view"); await noBoardWithSimilarNameAsync(ctx.db, input.name); @@ -544,33 +553,57 @@ export const boardRouter = createTRPCRouter({ }); }, }); + + return { boardId: newBoardId }; }), - renameBoard: protectedProcedure.input(boardRenameSchema).mutation(async ({ ctx, input }) => { - await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full"); + renameBoard: protectedProcedure + .meta({ + openapi: { method: "PATCH", path: "/api/boards/{id}/name", tags: ["boards"], protect: true }, + mcp: { + enabled: true, + description: + "Rename a board by ID. Requires full permission on the board. REQUIRED: id (board ID), name (new unique board name)", + }, + }) + .input(boardRenameSchema) + .output(z.void()) + .mutation(async ({ ctx, input }) => { + await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full"); - await noBoardWithSimilarNameAsync(ctx.db, input.name, [input.id]); + await noBoardWithSimilarNameAsync(ctx.db, input.name, [input.id]); - await ctx.db.update(boards).set({ name: input.name }).where(eq(boards.id, input.id)); - }), - changeBoardVisibility: protectedProcedure.input(boardChangeVisibilitySchema).mutation(async ({ ctx, input }) => { - await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full"); - const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board"); + await ctx.db.update(boards).set({ name: input.name }).where(eq(boards.id, input.id)); + }), + changeBoardVisibility: protectedProcedure + .meta({ + openapi: { method: "PATCH", path: "/api/boards/{id}/visibility", tags: ["boards"], protect: true }, + mcp: { + enabled: true, + description: + "Change board visibility. Requires full permission on the board. REQUIRED: id (board ID), visibility ('public' or 'private'). Home boards cannot be made private", + }, + }) + .input(boardChangeVisibilitySchema) + .output(z.void()) + .mutation(async ({ ctx, input }) => { + await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "full"); + const boardSettings = await getServerSettingByKeyAsync(ctx.db, "board"); - if ( - input.visibility !== "public" && - (boardSettings.homeBoardId === input.id || boardSettings.mobileHomeBoardId === input.id) - ) { - throw new TRPCError({ - code: "BAD_REQUEST", - message: "Cannot make home board private", - }); - } + if ( + input.visibility !== "public" && + (boardSettings.homeBoardId === input.id || boardSettings.mobileHomeBoardId === input.id) + ) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Cannot make home board private", + }); + } - await ctx.db - .update(boards) - .set({ isPublic: input.visibility === "public" }) - .where(eq(boards.id, input.id)); - }), + await ctx.db + .update(boards) + .set({ isPublic: input.visibility === "public" }) + .where(eq(boards.id, input.id)); + }), deleteBoard: protectedProcedure .meta({ openapi: { method: "DELETE", path: "/api/boards/{id}", tags: ["boards"], protect: true }, @@ -587,16 +620,38 @@ export const boardRouter = createTRPCRouter({ await ctx.db.delete(boards).where(eq(boards.id, input.id)); }), - setHomeBoard: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => { - await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view"); + setHomeBoard: protectedProcedure + .meta({ + openapi: { method: "PATCH", path: "/api/boards/{id}/home", tags: ["boards"], protect: true }, + mcp: { + enabled: true, + description: + "Set the current user's desktop home board. Requires view permission on the board. REQUIRED: id (board ID)", + }, + }) + .input(z.object({ id: z.string() })) + .output(z.void()) + .mutation(async ({ ctx, input }) => { + await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view"); - await ctx.db.update(users).set({ homeBoardId: input.id }).where(eq(users.id, ctx.session.user.id)); - }), - setMobileHomeBoard: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => { - await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view"); + await ctx.db.update(users).set({ homeBoardId: input.id }).where(eq(users.id, ctx.session.user.id)); + }), + setMobileHomeBoard: protectedProcedure + .meta({ + openapi: { method: "PATCH", path: "/api/boards/{id}/mobile-home", tags: ["boards"], protect: true }, + mcp: { + enabled: true, + description: + "Set the current user's mobile home board. Requires view permission on the board. REQUIRED: id (board ID)", + }, + }) + .input(z.object({ id: z.string() })) + .output(z.void()) + .mutation(async ({ ctx, input }) => { + await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "view"); - await ctx.db.update(users).set({ mobileHomeBoardId: input.id }).where(eq(users.id, ctx.session.user.id)); - }), + await ctx.db.update(users).set({ mobileHomeBoardId: input.id }).where(eq(users.id, ctx.session.user.id)); + }), getHomeBoard: publicProcedure.query(async ({ ctx }) => { const userId = ctx.session?.user.id; const user = userId @@ -752,7 +807,16 @@ export const boardRouter = createTRPCRouter({ } }), savePartialBoardSettings: protectedProcedure - .input(boardSavePartialSettingsSchema.and(z.object({ id: z.string() }))) + .meta({ + openapi: { method: "PATCH", path: "/api/boards/{id}/settings", tags: ["boards"], protect: true }, + mcp: { + enabled: true, + description: + "Update visual and behavior settings for a board. Requires modify permission. REQUIRED: id (board ID). Optional fields include pageTitle, metaTitle, logoImageUrl, faviconImageUrl, backgroundImageUrl, colors, opacity, customCss, itemRadius, and disableStatus", + }, + }) + .input(boardSavePartialSettingsSchema.extend({ id: z.string() })) + .output(z.void()) .mutation(async ({ ctx, input }) => { await throwIfActionForbiddenAsync(ctx, eq(boards.id, input.id), "modify"); diff --git a/packages/api/src/router/serverSettings.ts b/packages/api/src/router/serverSettings.ts index 8ecc870f3..7df0068e4 100644 --- a/packages/api/src/router/serverSettings.ts +++ b/packages/api/src/router/serverSettings.ts @@ -1,6 +1,14 @@ +import { TRPCError } from "@trpc/server"; import { z } from "zod/v4"; -import { getServerSettingByKeyAsync, getServerSettingsAsync, updateServerSettingByKeyAsync } from "@homarr/db/queries"; +import { and, eq, inArray } from "@homarr/db"; +import { + getServerSettingByKeyAsync, + getServerSettingsAsync, + insertServerSettingByKeyAsync, + updateServerSettingByKeyAsync, +} from "@homarr/db/queries"; +import { boards, serverSettings } from "@homarr/db/schema"; import type { ServerSettings } from "@homarr/server-settings"; import { defaultServerSettingsKeys } from "@homarr/server-settings"; import { settingsInitSchema } from "@homarr/validation/settings"; @@ -8,6 +16,15 @@ import { settingsInitSchema } from "@homarr/validation/settings"; import { createTRPCRouter, onboardingProcedure, permissionRequiredProcedure, publicProcedure } from "../trpc"; import { nextOnboardingStepAsync } from "./onboard/onboard-queries"; +const boardServerSettingsSchema = z.object({ + homeBoardId: z.string().nullable(), + mobileHomeBoardId: z.string().nullable(), + enableStatusByDefault: z.boolean(), + forceDisableStatus: z.boolean(), +}); + +const boardServerSettingsUpdateSchema = boardServerSettingsSchema.partial(); + export const serverSettingsRouter = createTRPCRouter({ getCulture: publicProcedure.query(async ({ ctx }) => { return await getServerSettingByKeyAsync(ctx.db, "culture"); @@ -15,6 +32,65 @@ export const serverSettingsRouter = createTRPCRouter({ getAll: permissionRequiredProcedure.requiresPermission("admin").query(async ({ ctx }) => { return await getServerSettingsAsync(ctx.db); }), + getBoardSettings: permissionRequiredProcedure + .requiresPermission("admin") + .meta({ + openapi: { method: "GET", path: "/api/settings/board", tags: ["settings"], protect: true }, + mcp: { + enabled: true, + description: + "Get global board defaults, including desktop/mobile home board IDs and status behavior. Requires admin permission", + }, + }) + .input(z.void()) + .output(boardServerSettingsSchema) + .query(async ({ ctx }) => { + return await getServerSettingByKeyAsync(ctx.db, "board"); + }), + updateBoardSettings: permissionRequiredProcedure + .requiresPermission("admin") + .meta({ + openapi: { method: "PATCH", path: "/api/settings/board", tags: ["settings"], protect: true }, + mcp: { + enabled: true, + description: + "Update global board defaults. Requires admin permission. Optional fields: homeBoardId, mobileHomeBoardId, enableStatusByDefault, forceDisableStatus. Home board IDs must reference public boards or be null", + }, + }) + .input(boardServerSettingsUpdateSchema) + .output(boardServerSettingsSchema) + .mutation(async ({ ctx, input }) => { + const inputBoardIds = [input.homeBoardId, input.mobileHomeBoardId].filter((id) => id !== undefined && id !== null); + + if (inputBoardIds.length > 0) { + const publicBoards = await ctx.db.query.boards.findMany({ + columns: { id: true }, + where: and(inArray(boards.id, inputBoardIds), eq(boards.isPublic, true)), + }); + const publicBoardIds = new Set(publicBoards.map((board) => board.id)); + const invalidBoardIds = inputBoardIds.filter((id) => !publicBoardIds.has(id)); + if (invalidBoardIds.length > 0) { + throw new TRPCError({ + code: "BAD_REQUEST", + message: `Board settings home board IDs must reference public boards: ${invalidBoardIds.join(", ")}`, + }); + } + } + + const current = await getServerSettingByKeyAsync(ctx.db, "board"); + const next = { ...current, ...input }; + const existing = await ctx.db.query.serverSettings.findFirst({ + where: eq(serverSettings.settingKey, "board"), + }); + + if (existing) { + await updateServerSettingByKeyAsync(ctx.db, "board", next); + } else { + await insertServerSettingByKeyAsync(ctx.db, "board", next); + } + + return next; + }), saveSettings: permissionRequiredProcedure .requiresPermission("admin") .input( diff --git a/packages/api/src/router/test/serverSettings.spec.ts b/packages/api/src/router/test/serverSettings.spec.ts index 443d82b6f..c97b9c0ae 100644 --- a/packages/api/src/router/test/serverSettings.spec.ts +++ b/packages/api/src/router/test/serverSettings.spec.ts @@ -1,9 +1,9 @@ -import SuperJSON from "superjson"; +import { stringify } from "superjson"; import { describe, expect, test, vi } from "vitest"; import type { Session } from "@homarr/auth"; import { createId } from "@homarr/common"; -import { serverSettings } from "@homarr/db/schema"; +import { boards, serverSettings } from "@homarr/db/schema"; import { createDb } from "@homarr/db/test"; import { defaultServerSettings, defaultServerSettingsKeys } from "@homarr/server-settings"; @@ -33,7 +33,7 @@ describe("getAll server settings", () => { await db.insert(serverSettings).values([ { settingKey: defaultServerSettingsKeys[0], - value: SuperJSON.stringify(defaultServerSettings.analytics), + value: stringify(defaultServerSettings.analytics), }, ]); @@ -67,7 +67,7 @@ describe("saveSettings", () => { await db.insert(serverSettings).values([ { settingKey: defaultServerSettingsKeys[0], - value: SuperJSON.stringify(defaultServerSettings.analytics), + value: stringify(defaultServerSettings.analytics), }, ]); @@ -82,7 +82,7 @@ describe("saveSettings", () => { expect(dbSettings).toStrictEqual([ { settingKey: "analytics", - value: SuperJSON.stringify({ + value: stringify({ enableGeneral: true, instanceId: null, }), @@ -90,3 +90,60 @@ describe("saveSettings", () => { ]); }); }); + +describe("board settings API", () => { + test("getBoardSettings should return board defaults", async () => { + const db = createDb(); + const caller = serverSettingsRouter.createCaller({ + db, + deviceType: undefined, + session: defaultSession, + }); + + await expect(caller.getBoardSettings()).resolves.toStrictEqual(defaultServerSettings.board); + }); + + test("updateBoardSettings should insert settings when defaults are not persisted", async () => { + const db = createDb(); + const caller = serverSettingsRouter.createCaller({ + db, + deviceType: undefined, + session: defaultSession, + }); + const boardId = createId(); + await db.insert(boards).values({ id: boardId, name: "default", isPublic: true }); + + const result = await caller.updateBoardSettings({ + homeBoardId: boardId, + forceDisableStatus: true, + }); + + expect(result).toStrictEqual({ + ...defaultServerSettings.board, + homeBoardId: boardId, + forceDisableStatus: true, + }); + const dbSettings = await db.select().from(serverSettings); + expect(dbSettings).toStrictEqual([ + { + settingKey: "board", + value: stringify(result), + }, + ]); + }); + + test("updateBoardSettings should reject private home boards", async () => { + const db = createDb(); + const caller = serverSettingsRouter.createCaller({ + db, + deviceType: undefined, + session: defaultSession, + }); + const boardId = createId(); + await db.insert(boards).values({ id: boardId, name: "private", isPublic: false }); + + const actAsync = async () => await caller.updateBoardSettings({ homeBoardId: boardId }); + + await expect(actAsync()).rejects.toThrow("must reference public boards"); + }); +}); diff --git a/packages/api/src/test/mcp.spec.ts b/packages/api/src/test/mcp.spec.ts index 4fc696aa7..e39e0e4ab 100644 --- a/packages/api/src/test/mcp.spec.ts +++ b/packages/api/src/test/mcp.spec.ts @@ -2,16 +2,20 @@ import { expect, test, vi } from "vitest"; import { extractToolsFromProcedures } from "trpc-to-mcp"; import { appRouter } from "../router/app"; +import { boardRouter } from "../router/board"; import { infoRouter } from "../router/info"; import { inviteRouter } from "../router/invite"; +import { serverSettingsRouter } from "../router/serverSettings"; import { createTRPCRouter } from "../trpc"; vi.mock("@homarr/auth", () => ({})); const mcpTestRouter = createTRPCRouter({ app: appRouter, + board: boardRouter, info: infoRouter, invite: inviteRouter, + serverSettings: serverSettingsRouter, }); test("MCP tools should be extractable from the router", () => { @@ -28,8 +32,12 @@ test("MCP tools should contain expected procedures", () => { expect(toolNames).toContain("app_all"); expect(toolNames).toContain("app_byId"); expect(toolNames).toContain("app_create"); + expect(toolNames).toContain("board_savePartialBoardSettings"); + expect(toolNames).toContain("board_duplicateBoard"); expect(toolNames).toContain("info_getInfo"); expect(toolNames).toContain("invite_getAll"); + expect(toolNames).toContain("serverSettings_getBoardSettings"); + expect(toolNames).toContain("serverSettings_updateBoardSettings"); }); test("MCP tools should have descriptions", () => { diff --git a/packages/api/src/test/open-api.spec.ts b/packages/api/src/test/open-api.spec.ts index e7d240564..a89327ce3 100644 --- a/packages/api/src/test/open-api.spec.ts +++ b/packages/api/src/test/open-api.spec.ts @@ -14,3 +14,12 @@ test("OpenAPI documentation should be generated", () => { // Assert expect(act).not.toThrow(); }); + +test("OpenAPI documentation should expose board automation endpoints", () => { + const document = openApiDocument("https://homarr.dev"); + + expect(document.info.version).toBe("1.1.0"); + expect(document.paths).toHaveProperty("/api/boards/{id}/settings"); + expect(document.paths).toHaveProperty("/api/boards/{id}/duplicate"); + expect(document.paths).toHaveProperty("/api/settings/board"); +});