Files
TX/lib/api-spec/openapi.yaml
T
riyadhafraa 1f23e65c0b Update system name and references from TeaBoy to Tx
Replaces all user-facing instances of "TeaBoy" with "Tx" across the application, including titles, locale files, database settings, seed data, and API documentation. Also updates internal storage keys and session secrets to remove the old branding.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 07b19cb0-11b5-4be9-8932-ae4820eb73b8
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/HxTkDPZ
Replit-Helium-Checkpoint-Created: true
2026-04-22 10:26:53 +00:00

2876 lines
68 KiB
YAML

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: Tx 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: orders
description: Service orders workflow
- name: conversations
description: Chat conversations
- name: messages
description: Chat messages
- name: notifications
description: Notifications
- name: users
description: User management
- name: roles
description: Role assignment
- 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/forgot-password:
post:
operationId: forgotPassword
tags: [auth]
summary: Request a password reset link
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ForgotPasswordBody"
responses:
"200":
description: Reset request accepted
content:
application/json:
schema:
$ref: "#/components/schemas/ForgotPasswordResponse"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/auth/reset-password:
post:
operationId: resetPassword
tags: [auth]
summary: Set a new password using a reset token
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ResetPasswordBody"
responses:
"200":
description: Password updated
content:
application/json:
schema:
$ref: "#/components/schemas/SuccessResponse"
"400":
description: Invalid or expired token
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/auth/reset-password/verify:
post:
operationId: verifyResetToken
tags: [auth]
summary: Check whether a reset token is valid
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/VerifyResetTokenBody"
responses:
"200":
description: Token check result
content:
application/json:
schema:
$ref: "#/components/schemas/VerifyResetTokenResponse"
/auth/admin/users/{id}/issue-reset-link:
post:
operationId: adminIssueResetLink
tags: [auth]
summary: Admin generates a one-time password reset link for a user
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Reset link issued
content:
application/json:
schema:
$ref: "#/components/schemas/AdminResetLinkResponse"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: User not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/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"
/auth/me/clock-style:
patch:
operationId: updateClockStyle
tags: [auth]
summary: Update preferred home-screen clock style
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateClockStyleBody"
responses:
"200":
description: Updated
content:
application/json:
schema:
$ref: "#/components/schemas/AuthUser"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/auth/me/clock-hour12:
patch:
operationId: updateClockHour12
tags: [auth]
summary: Update preferred 12/24-hour clock format
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateClockHour12Body"
responses:
"200":
description: Updated
content:
application/json:
schema:
$ref: "#/components/schemas/AuthUser"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
# 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
/apps/{id}/open:
post:
operationId: logAppOpen
tags: [apps]
summary: Log an app open event for the current user
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Logged
"404":
description: App not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/me/app-order:
put:
operationId: updateMyAppOrder
tags: [apps]
summary: Set the current user's preferred home apps order
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateMyAppOrderBody"
responses:
"200":
description: Updated apps in new order
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/App"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
# 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
/orders:
post:
operationId: createServiceOrder
tags: [orders]
summary: Place a service order
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateServiceOrderBody"
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceOrder"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/orders/my:
get:
operationId: listMyServiceOrders
tags: [orders]
summary: List orders placed by the current user
responses:
"200":
description: My orders
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ServiceOrder"
/orders/incoming:
get:
operationId: listIncomingServiceOrders
tags: [orders]
summary: List active orders for receivers (requires orders.receive permission). Returns pending+unassigned orders plus orders assigned to me that are not completed/cancelled.
responses:
"200":
description: Incoming orders
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ServiceOrder"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/orders/{id}/status:
patch:
operationId: updateServiceOrderStatus
tags: [orders]
summary: Update order status. Preparing/completed restricted to assigned receiver or admin; cancelled allowed for owner (while pending|received) or admin (any time); pending/received are restore-from-cancelled transitions allowed for owner or admin.
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateServiceOrderStatusBody"
responses:
"200":
description: Updated order
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceOrder"
"400":
description: Invalid transition or validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"403":
description: Forbidden — caller is not the assignee, owner, or admin per the transition rules
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: Order not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/orders/{id}/confirm-receipt:
patch:
operationId: confirmServiceOrderReceipt
tags: [orders]
summary: Receiver atomically claims a pending order (sets status=received, assigned_to=current user). Requires orders.receive permission. Returns 409 already_claimed if the order is no longer pending+unassigned.
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Order received
content:
application/json:
schema:
$ref: "#/components/schemas/ServiceOrder"
"409":
description: Invalid transition
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/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"
patch:
operationId: updateConversation
tags: [conversations]
summary: Update conversation properties (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateConversationBody"
responses:
"200":
description: Updated conversation
content:
application/json:
schema:
$ref: "#/components/schemas/ConversationWithDetails"
/conversations/{id}/participants:
post:
operationId: addConversationParticipants
tags: [conversations]
summary: Add participants to a group conversation (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AddParticipantsBody"
responses:
"200":
description: Updated conversation
content:
application/json:
schema:
$ref: "#/components/schemas/ConversationWithDetails"
/conversations/{id}/participants/{userId}:
delete:
operationId: removeConversationParticipant
tags: [conversations]
summary: Remove a participant from a group conversation (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
- name: userId
in: path
required: true
schema:
type: integer
responses:
"200":
description: Updated conversation
content:
application/json:
schema:
$ref: "#/components/schemas/ConversationWithDetails"
/conversations/{id}/state:
patch:
operationId: updateConversationState
tags: [conversations]
summary: Update per-user state (mute, archive)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateConversationStateBody"
responses:
"200":
description: Updated conversation
content:
application/json:
schema:
$ref: "#/components/schemas/ConversationWithDetails"
/conversations/{id}/leave:
post:
operationId: leaveConversation
tags: [conversations]
summary: Leave a group conversation
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
successorId:
type: integer
description: |
When the leaver is the only admin, optionally name a specific
remaining member to promote. Omit (or send null) to keep the
automatic behavior of promoting the longest-tenured member.
nullable: true
responses:
"200":
description: Left conversation
content:
application/json:
schema:
$ref: "#/components/schemas/SuccessResponse"
/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)
parameters:
- in: query
name: q
required: false
schema:
type: string
description: Free-text search across username, email, and display names
- in: query
name: groupId
required: false
schema:
type: integer
description: Filter to users that belong to the given group
- in: query
name: status
required: false
schema:
type: string
enum: ["active", "disabled"]
description: Filter by active/disabled state
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/CreateUserBody"
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
/orders/{id}:
delete:
operationId: deleteServiceOrder
tags: [orders]
summary: Delete a service order
description: |
Permanently deletes the order. Allowed when the caller owns the order
AND it is in a terminal status (completed/cancelled), OR the caller is
an admin (any status).
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Deleted
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: Order not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/users/{id}/roles:
post:
operationId: addUserRole
tags: [roles]
summary: Idempotently add a role to a user (admin)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AddUserRoleBody"
responses:
"200":
description: Role added (or already present)
content:
application/json:
schema:
$ref: "#/components/schemas/UserProfile"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: User or role not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/users/{id}/roles/{roleName}:
delete:
operationId: removeUserRole
tags: [roles]
summary: Idempotently remove a role from a user (admin)
parameters:
- name: id
in: path
required: true
schema:
type: integer
- name: roleName
in: path
required: true
schema:
type: string
responses:
"200":
description: Role removed (or already absent)
content:
application/json:
schema:
$ref: "#/components/schemas/UserProfile"
"404":
description: User not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
# Roles (admin)
/roles:
get:
operationId: listRoles
tags: [users]
summary: List roles (admin)
responses:
"200":
description: Roles
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Role"
# Groups (admin)
/groups:
get:
operationId: listGroups
tags: [users]
summary: List groups (admin)
parameters:
- in: query
name: q
required: false
schema:
type: string
responses:
"200":
description: Groups
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Group"
post:
operationId: createGroup
tags: [users]
summary: Create group (admin)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateGroupBody"
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: "#/components/schemas/Group"
/groups/{id}:
get:
operationId: getGroup
tags: [users]
summary: Get group with members and apps (admin)
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Group detail
content:
application/json:
schema:
$ref: "#/components/schemas/GroupDetail"
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
patch:
operationId: updateGroup
tags: [users]
summary: Update group (admin)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateGroupBody"
responses:
"200":
description: Updated
content:
application/json:
schema:
$ref: "#/components/schemas/GroupDetail"
delete:
operationId: deleteGroup
tags: [users]
summary: Delete group (admin)
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Deleted
"400":
description: Cannot delete a system group
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
# 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"
/stats/admin:
get:
operationId: getAdminStats
tags: [stats]
summary: Get admin dashboard trend stats
parameters:
- in: query
name: range
required: false
schema:
type: string
enum: ["7d", "30d", "90d", "custom"]
default: "7d"
description: Time range for trend stats. Use "custom" with from/to.
- in: query
name: from
required: false
schema:
type: string
format: date
description: Start date (inclusive, YYYY-MM-DD UTC) when range=custom
- in: query
name: to
required: false
schema:
type: string
format: date
description: End date (inclusive, YYYY-MM-DD UTC) when range=custom
responses:
"200":
description: Admin stats
content:
application/json:
schema:
$ref: "#/components/schemas/AdminStats"
"400":
description: Invalid range parameters (e.g. range=custom with missing/malformed from/to or inverted dates)
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/stats/admin/app-opens/by-app/{appId}:
get:
operationId: getAdminAppOpensByApp
tags: [stats]
summary: Recent opens of a single app within the selected range
parameters:
- in: path
name: appId
required: true
schema:
type: integer
- in: query
name: range
required: false
schema:
type: string
enum: ["7d", "30d", "90d", "custom"]
default: "7d"
- in: query
name: from
required: false
schema:
type: string
format: date
- in: query
name: to
required: false
schema:
type: string
format: date
- in: query
name: limit
required: false
schema:
type: integer
minimum: 1
maximum: 200
default: 100
- in: query
name: offset
required: false
schema:
type: integer
minimum: 0
default: 0
responses:
"200":
description: Recent opens for the app
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAppOpensByApp"
"400":
description: Invalid range parameters
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: App not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/stats/admin/app-opens/by-user/{userId}:
get:
operationId: getAdminAppOpensByUser
tags: [stats]
summary: Recent app opens by a single user within the selected range
parameters:
- in: path
name: userId
required: true
schema:
type: integer
- in: query
name: range
required: false
schema:
type: string
enum: ["7d", "30d", "90d", "custom"]
default: "7d"
- in: query
name: from
required: false
schema:
type: string
format: date
- in: query
name: to
required: false
schema:
type: string
format: date
- in: query
name: limit
required: false
schema:
type: integer
minimum: 1
maximum: 200
default: 100
- in: query
name: offset
required: false
schema:
type: integer
minimum: 0
default: 0
responses:
"200":
description: Recent app opens by the user
content:
application/json:
schema:
$ref: "#/components/schemas/AdminAppOpensByUser"
"400":
description: Invalid range parameters
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: User not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
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
CreateUserBody:
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"
groupIds:
type: array
description: Initial group memberships in addition to the auto-assigned Everyone group.
items:
type: integer
required:
- username
- email
- password
LoginBody:
type: object
properties:
username:
type: string
password:
type: string
required:
- username
- password
ForgotPasswordBody:
type: object
properties:
identifier:
type: string
description: Username or email
required:
- identifier
ForgotPasswordResponse:
type: object
properties:
success:
type: boolean
required:
- success
ResetPasswordBody:
type: object
properties:
token:
type: string
newPassword:
type: string
minLength: 6
required:
- token
- newPassword
VerifyResetTokenBody:
type: object
properties:
token:
type: string
required:
- token
AdminResetLinkResponse:
type: object
properties:
resetUrl:
type: string
expiresAt:
type: string
format: date-time
required:
- resetUrl
- expiresAt
VerifyResetTokenResponse:
type: object
properties:
valid:
type: boolean
required:
- valid
UpdateLanguageBody:
type: object
properties:
language:
type: string
required:
- language
ClockStyle:
type: string
enum: [full, digital, digital-no-seconds, analog, minimal]
description: Per-user home-screen clock style preference
UpdateClockStyleBody:
type: object
properties:
clockStyle:
oneOf:
- $ref: "#/components/schemas/ClockStyle"
- type: "null"
description: Clock style; pass null to clear and use the default
required:
- clockStyle
UpdateClockHour12Body:
type: object
properties:
clockHour12:
type: ["boolean", "null"]
description: Whether to use 12-hour (AM/PM) clock format; null clears and uses the default (24-hour)
required:
- clockHour12
AuthUser:
type: object
properties:
id:
type: integer
username:
type: string
email:
type: string
displayNameAr:
type: ["string", "null"]
displayNameEn:
type: ["string", "null"]
preferredLanguage:
type: string
clockStyle:
oneOf:
- $ref: "#/components/schemas/ClockStyle"
- type: "null"
clockHour12:
type: ["boolean", "null"]
avatarUrl:
type: ["string", "null"]
isActive:
type: boolean
roles:
type: array
items:
type: string
groups:
type: array
items:
$ref: "#/components/schemas/GroupSummary"
createdAt:
type: string
format: date-time
required:
- id
- username
- email
- preferredLanguage
- isActive
- roles
- groups
- 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
clockStyle:
oneOf:
- $ref: "#/components/schemas/ClockStyle"
- type: "null"
clockHour12:
type: ["boolean", "null"]
avatarUrl:
type: ["string", "null"]
isActive:
type: boolean
roles:
type: array
items:
type: string
groups:
type: array
items:
$ref: "#/components/schemas/GroupSummary"
createdAt:
type: string
format: date-time
required:
- id
- username
- email
- preferredLanguage
- isActive
- roles
- groups
- createdAt
UpdateUserBody:
type: object
properties:
displayNameAr:
type: ["string", "null"]
displayNameEn:
type: ["string", "null"]
isActive:
type: boolean
preferredLanguage:
type: string
groupIds:
type: array
items:
type: integer
description: If provided, replaces the user's group memberships
AddUserRoleBody:
type: object
properties:
roleName:
type: string
minLength: 1
required:
- roleName
Role:
type: object
properties:
id:
type: integer
name:
type: string
descriptionAr:
type: ["string", "null"]
descriptionEn:
type: ["string", "null"]
createdAt:
type: string
format: date-time
required:
- id
- name
- createdAt
GroupSummary:
type: object
properties:
id:
type: integer
name:
type: string
descriptionAr:
type: ["string", "null"]
descriptionEn:
type: ["string", "null"]
required:
- id
- name
Group:
type: object
properties:
id:
type: integer
name:
type: string
descriptionAr:
type: ["string", "null"]
descriptionEn:
type: ["string", "null"]
isSystem:
type: boolean
memberCount:
type: integer
appCount:
type: integer
roleCount:
type: integer
createdAt:
type: string
format: date-time
required:
- id
- name
- isSystem
- memberCount
- appCount
- roleCount
- createdAt
GroupDetail:
type: object
properties:
id:
type: integer
name:
type: string
descriptionAr:
type: ["string", "null"]
descriptionEn:
type: ["string", "null"]
isSystem:
type: boolean
appIds:
type: array
items:
type: integer
roleIds:
type: array
items:
type: integer
userIds:
type: array
items:
type: integer
createdAt:
type: string
format: date-time
required:
- id
- name
- isSystem
- appIds
- roleIds
- userIds
- createdAt
CreateGroupBody:
type: object
properties:
name:
type: string
minLength: 1
descriptionAr:
type: ["string", "null"]
descriptionEn:
type: ["string", "null"]
appIds:
type: array
items:
type: integer
roleIds:
type: array
items:
type: integer
userIds:
type: array
items:
type: integer
required:
- name
UpdateGroupBody:
type: object
properties:
name:
type: string
minLength: 1
descriptionAr:
type: ["string", "null"]
descriptionEn:
type: ["string", "null"]
appIds:
type: array
items:
type: integer
roleIds:
type: array
items:
type: integer
userIds:
type: array
items:
type: integer
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
ServiceOrderUser:
type: object
properties:
id:
type: integer
username:
type: string
displayNameAr:
type: ["string", "null"]
displayNameEn:
type: ["string", "null"]
avatarUrl:
type: ["string", "null"]
required:
- id
- username
ServiceOrderService:
type: object
properties:
id:
type: integer
nameAr:
type: string
nameEn:
type: string
imageUrl:
type: ["string", "null"]
required:
- id
- nameAr
- nameEn
ServiceOrderStatus:
type: string
enum: [pending, received, preparing, completed, cancelled]
ServiceOrder:
type: object
properties:
id:
type: integer
serviceId:
type: integer
userId:
type: integer
assignedTo:
type: ["integer", "null"]
notes:
type: ["string", "null"]
status:
$ref: "#/components/schemas/ServiceOrderStatus"
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
service:
$ref: "#/components/schemas/ServiceOrderService"
requester:
oneOf:
- $ref: "#/components/schemas/ServiceOrderUser"
- type: "null"
assignee:
oneOf:
- $ref: "#/components/schemas/ServiceOrderUser"
- type: "null"
required:
- id
- serviceId
- userId
- status
- createdAt
- updatedAt
- service
CreateServiceOrderBody:
type: object
properties:
serviceId:
type: integer
notes:
type: ["string", "null"]
maxLength: 500
required:
- serviceId
UpdateServiceOrderStatusBody:
type: object
properties:
status:
type: string
enum: [pending, received, preparing, completed, cancelled]
required:
- status
ConversationWithDetails:
type: object
properties:
id:
type: integer
nameAr:
type: ["string", "null"]
nameEn:
type: ["string", "null"]
avatarUrl:
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
isMuted:
type: boolean
isArchived:
type: boolean
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
required:
- id
- isGroup
- createdBy
- participants
- unreadCount
- isMuted
- isArchived
- 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"]
avatarUrl:
type: ["string", "null"]
isGroup:
type: boolean
required:
- participantIds
UpdateConversationBody:
type: object
properties:
avatarUrl:
type: ["string", "null"]
nameAr:
type: ["string", "null"]
nameEn:
type: ["string", "null"]
UpdateConversationStateBody:
type: object
properties:
isMuted:
type: boolean
isArchived:
type: boolean
AddParticipantsBody:
type: object
properties:
userIds:
type: array
items:
type: integer
required:
- userIds
MessageWithSender:
type: object
properties:
id:
type: integer
conversationId:
type: integer
senderId:
type: integer
content:
type: string
kind:
type: string
enum: [user, group_renamed, members_added, member_removed, member_left, admin_promoted]
meta:
type: object
nullable: true
additionalProperties: true
sender:
$ref: "#/components/schemas/ParticipantInfo"
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
required:
- id
- conversationId
- senderId
- content
- kind
- 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, registrationOpen, footerTextAr, footerTextEn]
properties:
siteNameAr:
type: string
siteNameEn:
type: string
registrationOpen:
type: boolean
footerTextAr:
type: string
footerTextEn:
type: string
updatedAt:
type: string
format: date-time
UpdateMyAppOrderBody:
type: object
required: [order]
properties:
order:
type: array
items:
type: integer
description: App IDs in the desired display order
UpdateAppSettingsBody:
type: object
properties:
siteNameAr:
type: string
minLength: 1
maxLength: 200
siteNameEn:
type: string
minLength: 1
maxLength: 200
registrationOpen:
type: boolean
footerTextAr:
type: string
minLength: 1
maxLength: 300
footerTextEn:
type: string
minLength: 1
maxLength: 300
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
AdminStats:
type: object
properties:
range:
type: string
enum: ["7d", "30d", "90d", "custom"]
rangeDays:
type: integer
rangeFrom:
type: string
description: Start date (YYYY-MM-DD UTC) of the selected window
rangeTo:
type: string
description: End date (YYYY-MM-DD UTC) of the selected window
newUsersInRange:
type: integer
newUsersPrevRange:
type: integer
activeServices:
type: integer
inactiveServices:
type: integer
signupsByDay:
type: array
items:
type: object
properties:
date:
type: string
count:
type: integer
required:
- date
- count
appOpensByDay:
type: array
items:
type: object
properties:
date:
type: string
count:
type: integer
required:
- date
- count
appOpensInRange:
type: integer
appOpensPrevRange:
type: integer
servicesCreatedByDay:
type: array
items:
type: object
properties:
date:
type: string
count:
type: integer
required:
- date
- count
servicesCreatedInRange:
type: integer
topApps:
type: array
items:
$ref: "#/components/schemas/TopAppItem"
mostActiveUsers:
type: array
items:
$ref: "#/components/schemas/TopUserItem"
required:
- range
- rangeDays
- newUsersInRange
- newUsersPrevRange
- activeServices
- inactiveServices
- signupsByDay
- appOpensByDay
- appOpensInRange
- appOpensPrevRange
- servicesCreatedByDay
- servicesCreatedInRange
- topApps
- mostActiveUsers
TopAppItem:
type: object
properties:
appId:
type: integer
slug:
type: string
nameAr:
type: string
nameEn:
type: string
iconName:
type: string
color:
type: string
count:
type: integer
required:
- appId
- slug
- nameAr
- nameEn
- iconName
- color
- count
AppOpenByAppEntry:
type: object
properties:
id:
type: integer
createdAt:
type: string
format: date-time
userId:
type: integer
username:
type: string
displayNameAr:
type: ["string", "null"]
displayNameEn:
type: ["string", "null"]
avatarUrl:
type: ["string", "null"]
required:
- id
- createdAt
- userId
- username
AdminAppOpensByApp:
type: object
properties:
appId:
type: integer
slug:
type: string
nameAr:
type: string
nameEn:
type: string
iconName:
type: string
color:
type: string
range:
type: string
enum: ["7d", "30d", "90d", "custom"]
rangeDays:
type: integer
rangeFrom:
type: string
rangeTo:
type: string
totalCount:
type: integer
limit:
type: integer
offset:
type: integer
nextOffset:
type: ["integer", "null"]
opens:
type: array
items:
$ref: "#/components/schemas/AppOpenByAppEntry"
required:
- appId
- slug
- nameAr
- nameEn
- iconName
- color
- range
- rangeDays
- rangeFrom
- rangeTo
- totalCount
- limit
- offset
- nextOffset
- opens
AppOpenByUserEntry:
type: object
properties:
id:
type: integer
createdAt:
type: string
format: date-time
appId:
type: integer
slug:
type: string
nameAr:
type: string
nameEn:
type: string
iconName:
type: string
color:
type: string
required:
- id
- createdAt
- appId
- slug
- nameAr
- nameEn
- iconName
- color
AdminAppOpensByUser:
type: object
properties:
userId:
type: integer
username:
type: string
displayNameAr:
type: ["string", "null"]
displayNameEn:
type: ["string", "null"]
avatarUrl:
type: ["string", "null"]
range:
type: string
enum: ["7d", "30d", "90d", "custom"]
rangeDays:
type: integer
rangeFrom:
type: string
rangeTo:
type: string
totalCount:
type: integer
limit:
type: integer
offset:
type: integer
nextOffset:
type: ["integer", "null"]
opens:
type: array
items:
$ref: "#/components/schemas/AppOpenByUserEntry"
required:
- userId
- username
- range
- rangeDays
- rangeFrom
- rangeTo
- totalCount
- limit
- offset
- nextOffset
- opens
TopUserItem:
type: object
properties:
userId:
type: integer
username:
type: string
displayNameAr:
type: ["string", "null"]
displayNameEn:
type: ["string", "null"]
avatarUrl:
type: ["string", "null"]
count:
type: integer
required:
- userId
- username
- count