{"name":"Web-IM Unified Plugins Server","status":"online","tenancy":"Send X-Tenant: <slug> (or ?tenant=) on every request; omitted = the \"root\" tenant. Sessions, users and all module data are isolated per tenant.","localization":"Send X-Lang: <code> (or Accept-Language) on any GET request for localized content — ?lang=<code> is accepted as a fallback. Add translations via PUT .../:id/translations/:lang","auth":{"note":"Modules require Authorization: Bearer <token>, except weather (fully public) and meals (publicly viewable — see plugins.meals.publicAccess)","login":"POST /auth/login {username|email, password}","logout":"POST /auth/logout","me":"GET /auth/me · PUT /auth/me {name?, avatar?, phone?, duty?, birthDate?, password?} (self-service)","avatars":"Profile photos are never inline: send avatar as a data URI once and it is stored as a file; user records carry GET /auth/avatar/:userId?v=<etag> (public, immutable, cached). Inline images in any other body field named *avatar* are replaced by the caller's avatar URL.","users":"GET/POST /auth/users and GET/PUT/DELETE /auth/users/:id (admin only)","sessions":"GET/DELETE /auth/users/:id/sessions — list or revoke a user's sessions (admin only)","permissions":"per-plugin read/write/delete abilities set on each user; admins have all. Module management routes enforce them."},"directory":{"note":"THE people feed. Every widget reads it once per page through plugins/shared/feed.ts (window-level cache shared by all plugin bundles) instead of per-module user lists.","list":"GET /directory?excludeMe=1 — all active users: id, username, name, email, organization, department, departmentId, duty, phone, avatar, role, birthDate","departments":"GET /directory/departments — [{department, count}]","aliases":"GET /chat/users, /tasks/assignees, /tasks/members, /calendar/assignees, /reservations/attendees, /phonebook — same rows, narrower projections; kept for older bundles"},"plugins":{"wall":{"list":"GET /wall (filters: all, pinned, my, pending)","getById":"GET /wall/:id","create":"POST /wall {message, media, pinned}","update":"PUT /wall/:id","delete":"DELETE /wall/:id","status":"PUT /wall/:id/status {active}","pin":"PUT /wall/:id/pin {pinned}","react":"POST /wall/:id/react {reaction}","comment":"POST /wall/:id/comments {message, parentId?}","deleteComment":"DELETE /wall/:id/comments/:commentId","settings":"GET/PUT /wall/settings"},"phonebook":{"note":"THE canonical user directory — a read-only view over active users (profile, department/organization from the departments module, duty, birthDate). Every plugin people list feeds from the same source.","list":"GET /phonebook?search=&department=&organization=&offset=&limit=","departments":"GET /phonebook/departments","person":"GET /phonebook/:id"},"survey":{"note":"Answer once per user; managers (write) create/manage; drafts hidden from plain users","list":"GET /survey","getById":"GET /survey/:id","respond":"POST /survey/:id/respond {answers: [{questionId, optionIndexes?|text?|rating?}]}","myResponse":"GET /survey/:id/my-response","results":"GET /survey/:id/results (managers always; respondents when publicResults)","create":"POST /survey {title, description?, anonymous?, publicResults?, closesAt?, questions} (write)","update":"PUT /survey/:id (write; questions only while an unanswered draft)","status":"PUT /survey/:id/status {status: 'open'|'closed'} (write)","delete":"DELETE /survey/:id (delete)"},"marketplace":{"note":"Employee classifieds — everyone manages their OWN listings; \"delete\" moderates others. Listings auto-expire to passive after durationDays.","list":"GET /marketplace?category=&city=&search=&minPrice=&maxPrice=&status=active|passive|sold|all&sortBy=newest|price_asc|price_desc|expiring_soon&attributes=<json>&mine=&offset=&limit=","categories":"GET /marketplace/categories","getById":"GET /marketplace/:id (increments viewsCount)","create":"POST /marketplace {title, description, category, price, currency?, negotiable?, city?, district?, images?, attributes?, durationDays?}","update":"PUT /marketplace/:id (owner)","status":"PUT /marketplace/:id/status {status: 'active'|'passive'|'sold'} (owner; re-activating resets expiry)","renew":"POST /marketplace/:id/renew {durationDays?} (owner)","delete":"DELETE /marketplace/:id (owner; removes its chat threads)","chatOpen":"POST /marketplace/:id/messages {text} (buyer opens/continues the thread)","chatThreads":"GET /marketplace/messages/threads (mine, buyer or seller)","chatThread":"GET /marketplace/messages/threads/:threadId","chatReply":"POST /marketplace/messages/threads/:threadId/reply {text}","chatRead":"PUT /marketplace/messages/threads/:threadId/read"},"calendar":{"note":"Personal events/tasks/reminders — visible to author + assignees","list":"GET /calendar?type=event|task|reminder&status=&from=&to=","counts":"GET /calendar/counts?from=&to=&tz=<getTimezoneOffset()>","assignees":"GET /calendar/assignees","getById":"GET /calendar/:id","create":"POST /calendar {type, title, start, end?, allDay?, description?, location?, color?, status?, assigneeIds?}","update":"PUT /calendar/:id (author)","taskStatus":"PUT /calendar/:id/status {status: 'todo'|'in_progress'|'done'} (author or assignee)","delete":"DELETE /calendar/:id (author)"},"chat":{"note":"DMs + group rooms, realtime over WebSocket with polling as fallback. End-to-end encrypted: clients encrypt with a per-room key; the server stores ciphertext envelopes and relays wrapped keys.","realtime":"WS /chat/ws — first frame {type:'auth', token, tenant?}; then events: {type:'message'|'messageDeleted'|'rooms'}","rooms":"GET /chat/rooms (with unread counts)","createRoom":"POST /chat/rooms {type:'direct', userId} | {type:'group', name, memberIds} (+ encrypted:true for E2EE)","room":"GET /chat/rooms/:id","rename":"PUT /chat/rooms/:id {name} (group admin)","addMembers":"POST /chat/rooms/:id/members {memberIds} (group admin)","removeMember":"DELETE /chat/rooms/:id/members/:userId (self-leave, or group admin)","deleteRoom":"DELETE /chat/rooms/:id (group admin)","messages":"GET /chat/rooms/:id/messages?after=|before=&limit=","send":"POST /chat/rooms/:id/messages {message} | {e2ee: {ct, iv, v}} (encrypted envelope)","publishKey":"PUT /chat/keys {publicKey: <ECDH JWK>}","publicKeys":"GET /chat/keys?userIds=a,b,c","roomKeyHolders":"GET /chat/rooms/:id/keys (who holds a wrapped key)","myRoomKey":"GET /chat/rooms/:id/keys/me (404 NO_ROOM_KEY until a member wraps one for you)","distributeKeys":"PUT /chat/rooms/:id/keys {keyVersion?, keys: [{userId, wrappedKey, iv}]}","deleteMessage":"DELETE /chat/rooms/:id/messages/:messageId (own; tombstoned)","markRead":"PUT /chat/rooms/:id/read","users":"GET /chat/users"},"tasks":{"note":"Kanban task management — boards with fixed columns todo|in_progress|review|done; personal tasks with boardId null","boards":"GET /tasks/boards (mine, with per-status counts + overdue)","createBoard":"POST /tasks/boards {name, description?, color?, memberIds?}","board":"GET /tasks/boards/:id (board + tasks grouped into columns)","updateBoard":"PUT /tasks/boards/:id {name?, description?, color?, memberIds?, archived?} (owner)","deleteBoard":"DELETE /tasks/boards/:id (owner; removes its tasks)","list":"GET /tasks?boardId=<id|personal>&status=&priority=&assignedToMe=&authoredByMe=&overdue=&search=&tag=","summary":"GET /tasks/summary?boardId=all|personal|<id> — byStatus, overdue, dueSoon, assignedToMe for the same scope as the list","tags":"GET /tasks/tags (distinct tags across my visible tasks)","create":"POST /tasks {title, boardId?, status?, priority?, dueDate?, tags?, checklist?, assigneeIds?, description?}","getById":"GET /tasks/:id (task + activity log)","update":"PUT /tasks/:id (author or board owner; logs priority/due/assignee changes)","move":"PUT /tasks/:id/move {status, order?} (any participant — kanban drag)","checklist":"PUT /tasks/:id/checklist/:itemId {done} (any participant)","comment":"POST /tasks/:id/comments {message}; DELETE /tasks/:id/comments/:commentId (own)","delete":"DELETE /tasks/:id (author or board owner)"},"departments":{"note":"Organization structure — everyone views the tree; building it needs \"write\", removing units \"delete\". Assigning users keeps their department/organization display strings in step.","tree":"GET /departments/tree (nested, with membersCount + headcount)","list":"GET /departments (flat, path-sorted)","unit":"GET /departments/:id (unit + direct members)","create":"POST /departments {name, parentId?, kind?: organization|department|team, description?, managerId?, order?} (write)","update":"PUT /departments/:id {name?, kind?, description?, order?, managerId?|null, parentId?|null — re-parent, cycle-safe} (write)","delete":"DELETE /departments/:id (delete; leaf units only, members become unassigned)","assign":"PUT /departments/:id/members {userIds} (write)","unassign":"DELETE /departments/:id/members/:userId (write)"},"reservations":{"note":"Bookable shared resources — meeting rooms, vehicles, equipment. Overlapping slots are refused (409 SLOT_TAKEN); resources with requiresApproval queue reservations as pending until a manager (write) decides. \"write\" manages resources; \"delete\" moderates.","resources":"GET /reservations/resources?type=room|vehicle|equipment|other&departmentId=&search=&all=","createResource":"POST /reservations/resources {name, type, description?, location?, capacity?, color?, departmentId?, requiresApproval?, attributes?, active?} (write)","resource":"GET /reservations/resources/:id","availability":"GET /reservations/resources/:id/availability?from=&to= (blocking reservations in the window)","updateResource":"PUT /reservations/resources/:id (write)","deleteResource":"DELETE /reservations/resources/:id (delete; removes its reservations)","list":"GET /reservations?resourceId=&status=&from=&to= (mine; &all=1 with write)","pending":"GET /reservations/pending — approval queue (write)","attendees":"GET /reservations/attendees","getById":"GET /reservations/:id","create":"POST /reservations {resourceId, title, start, end, description?, attendeeIds?}","update":"PUT /reservations/:id (reserver; a time change re-checks conflicts and may re-queue approval)","decide":"PUT /reservations/:id/status {status: 'approved'|'rejected', note?} (write)","cancel":"PUT /reservations/:id/cancel (reserver)","delete":"DELETE /reservations/:id (reserver)"},"birthdays":{"note":"Feeds from THE user directory: a teammate = an active user with birthDate on their profile (set via PUT /auth/users/:id or POST /birthdays/members {id, birthDate}). Wishes/celebrations keyed by user id.","settings":"GET /birthdays/settings · PUT /birthdays/settings {upcomingDays?, celebrateDaysAhead?} (admin only) — tenant-wide upcoming window & early-celebration limit","list":"GET /birthdays","getById":"GET /birthdays/:id","celebrate":"POST /birthdays/:id/celebrate","sendWish":"POST /birthdays/:id/wishes","addMember":"POST /birthdays/members","deleteMember":"DELETE /birthdays/:id","setTranslations":"PUT /birthdays/:id/translations/:lang"},"manifesto":{"list":"GET /manifesto","getById":"GET /manifesto/:id","status":"GET /manifesto/status/:clientId","confirm":"POST /manifesto/confirm","confirmations":"GET /manifesto/confirmations","reset":"POST /manifesto/reset","setTranslations":"PUT /manifesto/:id/translations/:lang"},"meals":{"publicAccess":"Browsing (GET /today, GET /?month=, GET /:id) needs no token. Rating, commenting and menu management require a session and answer 401 {code:\"AUTH_REQUIRED\"} without one.","today":"GET /meals/today","month":"GET /meals?month=YYYY-MM","getById":"GET /meals/:id (includes comments)","vote":"POST /meals/:id/vote {vote: 1-5} (sign-in required; served meals only, one vote per user)","comment":"POST /meals/:id/comments {message, senderName?} (sign-in required)","deleteComment":"DELETE /meals/:id/comments/:commentId (sign-in required, own comment)","upsertMenu":"PUT /meals/menu/:date {items, note?} (requires write)","deleteMenu":"DELETE /meals/menu/:id (requires delete)"},"weather":{"forecast":"GET /weather/forecast?latitude=..&longitude=..&units=metric|imperial (cached server-side, refreshed by a background job)","locations":"GET /weather/locations","trackLocation":"POST /weather/locations {q | latitude+longitude, units?}","untrackLocation":"DELETE /weather/locations/:key","search":"GET /weather/search?q=istanbul","reverse":"GET /weather/reverse?latitude=..&longitude=.."}},"health":"GET /health","jobs":"GET /jobs"}