openapi: 3.1.0 info: # Do not change the title, if the title changes, the import paths will be broken title: Api version: 0.1.0 description: TeaBoy OS API specification servers: - url: /api description: Base API path tags: - name: health description: Health operations - name: auth description: Authentication - name: apps description: App management - name: services description: Internal services (khidmati) - name: conversations description: Chat conversations - name: messages description: Chat messages - name: notifications description: Notifications - name: users description: User management - name: stats description: Dashboard stats - name: storage description: Object storage upload and serving endpoints paths: /healthz: get: operationId: healthCheck tags: [health] summary: Health check responses: "200": description: Healthy content: application/json: schema: $ref: "#/components/schemas/HealthStatus" # Auth /auth/register: post: operationId: register tags: [auth] summary: Register a new user requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RegisterBody" responses: "201": description: Registered content: application/json: schema: $ref: "#/components/schemas/AuthUser" "400": description: Validation error content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /auth/login: post: operationId: login tags: [auth] summary: Login requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LoginBody" responses: "200": description: Logged in content: application/json: schema: $ref: "#/components/schemas/AuthUser" "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /auth/logout: post: operationId: logout tags: [auth] summary: Logout responses: "200": description: Logged out content: application/json: schema: $ref: "#/components/schemas/SuccessResponse" /auth/me: get: operationId: getMe tags: [auth] summary: Get current user responses: "200": description: Current user content: application/json: schema: $ref: "#/components/schemas/AuthUser" "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" /auth/me/language: patch: operationId: updateLanguage tags: [auth] summary: Update preferred language requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateLanguageBody" responses: "200": description: Updated content: application/json: schema: $ref: "#/components/schemas/AuthUser" # Apps /apps: get: operationId: listApps tags: [apps] summary: List all active apps responses: "200": description: Apps list content: application/json: schema: type: array items: $ref: "#/components/schemas/App" post: operationId: createApp tags: [apps] summary: Create a new app (admin) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateAppBody" responses: "201": description: Created content: application/json: schema: $ref: "#/components/schemas/App" /apps/{id}: get: operationId: getApp tags: [apps] summary: Get app by ID parameters: - name: id in: path required: true schema: type: integer responses: "200": description: App content: application/json: schema: $ref: "#/components/schemas/App" patch: operationId: updateApp tags: [apps] summary: Update app (admin) parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateAppBody" responses: "200": description: Updated content: application/json: schema: $ref: "#/components/schemas/App" delete: operationId: deleteApp tags: [apps] summary: Delete app (admin) parameters: - name: id in: path required: true schema: type: integer responses: "204": description: Deleted # Settings /settings: get: operationId: getAppSettings tags: [settings] summary: Get application settings (public) responses: "200": description: Current settings content: application/json: schema: $ref: "#/components/schemas/AppSettings" patch: operationId: updateAppSettings tags: [settings] summary: Update application settings (admin) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateAppSettingsBody" responses: "200": description: Updated settings content: application/json: schema: $ref: "#/components/schemas/AppSettings" "400": description: Invalid input content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" # Storage /storage/uploads/request-url: post: operationId: requestUploadUrl tags: [storage] summary: Request a presigned URL for file upload requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RequestUploadUrlBody" responses: "200": description: Presigned upload URL generated content: application/json: schema: $ref: "#/components/schemas/RequestUploadUrlResponse" "400": description: Invalid input content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" # Services /services: get: operationId: listServices tags: [services] summary: List all services responses: "200": description: Services list content: application/json: schema: type: array items: $ref: "#/components/schemas/Service" post: operationId: createService tags: [services] summary: Create a service (admin) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateServiceBody" responses: "201": description: Created content: application/json: schema: $ref: "#/components/schemas/Service" /services/{id}: get: operationId: getService tags: [services] summary: Get service by ID parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Service content: application/json: schema: $ref: "#/components/schemas/Service" patch: operationId: updateService tags: [services] summary: Update service (admin) parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateServiceBody" responses: "200": description: Updated content: application/json: schema: $ref: "#/components/schemas/Service" delete: operationId: deleteService tags: [services] summary: Delete service (admin) parameters: - name: id in: path required: true schema: type: integer responses: "204": description: Deleted /service-categories: get: operationId: listServiceCategories tags: [services] summary: List service categories responses: "200": description: Categories content: application/json: schema: type: array items: $ref: "#/components/schemas/ServiceCategory" # Conversations /conversations: get: operationId: listConversations tags: [conversations] summary: List conversations for current user responses: "200": description: Conversations content: application/json: schema: type: array items: $ref: "#/components/schemas/ConversationWithDetails" post: operationId: createConversation tags: [conversations] summary: Create a conversation requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CreateConversationBody" responses: "201": description: Created content: application/json: schema: $ref: "#/components/schemas/ConversationWithDetails" /conversations/{id}: get: operationId: getConversation tags: [conversations] summary: Get conversation by ID parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Conversation content: application/json: schema: $ref: "#/components/schemas/ConversationWithDetails" /conversations/{id}/messages: get: operationId: listMessages tags: [messages] summary: List messages in a conversation parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Messages content: application/json: schema: type: array items: $ref: "#/components/schemas/MessageWithSender" post: operationId: sendMessage tags: [messages] summary: Send a message parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SendMessageBody" responses: "201": description: Message sent content: application/json: schema: $ref: "#/components/schemas/MessageWithSender" /conversations/{id}/read: post: operationId: markConversationRead tags: [messages] summary: Mark all messages in conversation as read parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Marked as read content: application/json: schema: $ref: "#/components/schemas/SuccessResponse" # Notifications /notifications: get: operationId: listNotifications tags: [notifications] summary: List notifications for current user responses: "200": description: Notifications content: application/json: schema: type: array items: $ref: "#/components/schemas/Notification" /notifications/{id}/read: patch: operationId: markNotificationRead tags: [notifications] summary: Mark notification as read parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Marked as read content: application/json: schema: $ref: "#/components/schemas/Notification" /notifications/read-all: post: operationId: markAllNotificationsRead tags: [notifications] summary: Mark all notifications as read responses: "200": description: Done content: application/json: schema: $ref: "#/components/schemas/SuccessResponse" # Users (admin) /users: get: operationId: listUsers tags: [users] summary: List all users (admin) responses: "200": description: Users content: application/json: schema: type: array items: $ref: "#/components/schemas/UserProfile" post: operationId: createUser tags: [users] summary: Create a user (admin) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RegisterBody" responses: "201": description: Created user content: application/json: schema: $ref: "#/components/schemas/UserProfile" "409": description: Username already taken /users/{id}: get: operationId: getUser tags: [users] summary: Get user by ID (admin) parameters: - name: id in: path required: true schema: type: integer responses: "200": description: User content: application/json: schema: $ref: "#/components/schemas/UserProfile" patch: operationId: updateUser tags: [users] summary: Update user (admin) parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/UpdateUserBody" responses: "200": description: Updated content: application/json: schema: $ref: "#/components/schemas/UserProfile" delete: operationId: deleteUser tags: [users] summary: Delete user (admin) parameters: - name: id in: path required: true schema: type: integer responses: "204": description: Deleted # Stats /stats/home: get: operationId: getHomeStats tags: [stats] summary: Get home screen stats responses: "200": description: Stats content: application/json: schema: $ref: "#/components/schemas/HomeStats" components: schemas: HealthStatus: type: object properties: status: type: string required: - status ErrorResponse: type: object properties: error: type: string required: - error SuccessResponse: type: object properties: success: type: boolean required: - success RegisterBody: type: object properties: username: type: string email: type: string password: type: string displayNameAr: type: ["string", "null"] displayNameEn: type: ["string", "null"] preferredLanguage: type: string default: "ar" required: - username - email - password LoginBody: type: object properties: username: type: string password: type: string required: - username - password UpdateLanguageBody: type: object properties: language: type: string required: - language AuthUser: type: object properties: id: type: integer username: type: string email: type: string displayNameAr: type: ["string", "null"] displayNameEn: type: ["string", "null"] preferredLanguage: type: string avatarUrl: type: ["string", "null"] isActive: type: boolean roles: type: array items: type: string createdAt: type: string format: date-time required: - id - username - email - preferredLanguage - isActive - roles - createdAt UserProfile: type: object properties: id: type: integer username: type: string email: type: string displayNameAr: type: ["string", "null"] displayNameEn: type: ["string", "null"] preferredLanguage: type: string avatarUrl: type: ["string", "null"] isActive: type: boolean roles: type: array items: type: string createdAt: type: string format: date-time required: - id - username - email - preferredLanguage - isActive - roles - createdAt UpdateUserBody: type: object properties: displayNameAr: type: ["string", "null"] displayNameEn: type: ["string", "null"] isActive: type: boolean preferredLanguage: type: string App: type: object properties: id: type: integer slug: type: string nameAr: type: string nameEn: type: string descriptionAr: type: ["string", "null"] descriptionEn: type: ["string", "null"] iconName: type: string route: type: string color: type: string isActive: type: boolean isSystem: type: boolean sortOrder: type: integer createdAt: type: string format: date-time updatedAt: type: string format: date-time required: - id - slug - nameAr - nameEn - iconName - route - color - isActive - isSystem - sortOrder - createdAt - updatedAt CreateAppBody: type: object properties: slug: type: string nameAr: type: string nameEn: type: string descriptionAr: type: ["string", "null"] descriptionEn: type: ["string", "null"] iconName: type: string route: type: string color: type: string isActive: type: boolean isSystem: type: boolean sortOrder: type: integer required: - slug - nameAr - nameEn - iconName - route - color UpdateAppBody: type: object properties: nameAr: type: string nameEn: type: string descriptionAr: type: ["string", "null"] descriptionEn: type: ["string", "null"] iconName: type: string route: type: string color: type: string isActive: type: boolean sortOrder: type: integer Service: type: object properties: id: type: integer categoryId: type: ["integer", "null"] nameAr: type: string nameEn: type: string descriptionAr: type: ["string", "null"] descriptionEn: type: ["string", "null"] imageUrl: type: ["string", "null"] price: type: ["string", "null"] isAvailable: type: boolean sortOrder: type: integer createdAt: type: string format: date-time updatedAt: type: string format: date-time required: - id - nameAr - nameEn - isAvailable - sortOrder - createdAt - updatedAt CreateServiceBody: type: object properties: categoryId: type: ["integer", "null"] nameAr: type: string nameEn: type: string descriptionAr: type: ["string", "null"] descriptionEn: type: ["string", "null"] imageUrl: type: ["string", "null"] price: type: ["string", "null"] isAvailable: type: boolean sortOrder: type: integer required: - nameAr - nameEn UpdateServiceBody: type: object properties: categoryId: type: ["integer", "null"] nameAr: type: string nameEn: type: string descriptionAr: type: ["string", "null"] descriptionEn: type: ["string", "null"] imageUrl: type: ["string", "null"] price: type: ["string", "null"] isAvailable: type: boolean sortOrder: type: integer ServiceCategory: type: object properties: id: type: integer nameAr: type: string nameEn: type: string sortOrder: type: integer createdAt: type: string format: date-time required: - id - nameAr - nameEn - sortOrder - createdAt ConversationWithDetails: type: object properties: id: type: integer nameAr: type: ["string", "null"] nameEn: type: ["string", "null"] isGroup: type: boolean createdBy: type: integer participants: type: array items: $ref: "#/components/schemas/ParticipantInfo" lastMessage: $ref: "#/components/schemas/MessageWithSender" nullable: true unreadCount: type: integer createdAt: type: string format: date-time updatedAt: type: string format: date-time required: - id - isGroup - createdBy - participants - unreadCount - createdAt - updatedAt ParticipantInfo: type: object properties: id: type: integer username: type: string displayNameAr: type: ["string", "null"] displayNameEn: type: ["string", "null"] avatarUrl: type: ["string", "null"] isAdmin: type: boolean required: - id - username - isAdmin CreateConversationBody: type: object properties: participantIds: type: array items: type: integer nameAr: type: ["string", "null"] nameEn: type: ["string", "null"] isGroup: type: boolean required: - participantIds MessageWithSender: type: object properties: id: type: integer conversationId: type: integer senderId: type: integer content: type: string sender: $ref: "#/components/schemas/ParticipantInfo" createdAt: type: string format: date-time updatedAt: type: string format: date-time required: - id - conversationId - senderId - content - sender - createdAt - updatedAt SendMessageBody: type: object properties: content: type: string required: - content Notification: type: object properties: id: type: integer userId: type: integer titleAr: type: string titleEn: type: string bodyAr: type: ["string", "null"] bodyEn: type: ["string", "null"] type: type: string isRead: type: boolean relatedId: type: ["integer", "null"] relatedType: type: ["string", "null"] createdAt: type: string format: date-time required: - id - userId - titleAr - titleEn - type - isRead - createdAt AppSettings: type: object required: [siteNameAr, siteNameEn] properties: siteNameAr: type: string siteNameEn: type: string updatedAt: type: string format: date-time UpdateAppSettingsBody: type: object properties: siteNameAr: type: string minLength: 1 maxLength: 200 siteNameEn: type: string minLength: 1 maxLength: 200 RequestUploadUrlBody: type: object required: [name, size, contentType] properties: name: type: string minLength: 1 size: type: integer minimum: 1 contentType: type: string minLength: 1 RequestUploadUrlResponse: type: object required: [uploadURL, objectPath] properties: uploadURL: type: string format: uri objectPath: type: string metadata: $ref: "#/components/schemas/RequestUploadUrlBody" HomeStats: type: object properties: totalApps: type: integer totalServices: type: integer unreadNotifications: type: integer unreadMessages: type: integer totalUsers: type: integer required: - totalApps - totalServices - unreadNotifications - unreadMessages - totalUsers