openapi: 3.0.0 info: title: Multi-Tenant SaaS Platform API description: API for Malaysian SME SaaS platform with industry-specific modules version: 1.0.0 contact: name: API Support email: support@platform.com servers: - url: https://api.platform.com/v1 description: Production server - url: https://staging-api.platform.com/v1 description: Staging server - url: http://localhost:8000/v1 description: Development server security: - BearerAuth: [] paths: # Authentication endpoints /auth/login: post: summary: User login tags: [Authentication] security: [] requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: type: string format: email password: type: string format: password tenant_slug: type: string description: Tenant identifier for login responses: '200': description: Login successful content: application/json: schema: type: object properties: access_token: type: string refresh_token: type: string user: $ref: '#/components/schemas/User' tenant: $ref: '#/components/schemas/Tenant' '401': description: Invalid credentials '404': description: Tenant not found /auth/logout: post: summary: User logout tags: [Authentication] responses: '200': description: Logout successful '401': description: Unauthorized /auth/refresh: post: summary: Refresh access token tags: [Authentication] security: [] requestBody: required: true content: application/json: schema: type: object required: [refresh_token] properties: refresh_token: type: string responses: '200': description: Token refreshed successfully content: application/json: schema: type: object properties: access_token: type: string '401': description: Invalid refresh token # Tenant management endpoints /tenants: get: summary: List tenants (admin only) tags: [Tenants] parameters: - name: page in: query type: integer default: 1 - name: limit in: query type: integer default: 20 - name: search in: query type: string responses: '200': description: List of tenants content: application/json: schema: type: object properties: tenants: type: array items: $ref: '#/components/schemas/Tenant' pagination: $ref: '#/components/schemas/Pagination' post: summary: Create new tenant tags: [Tenants] requestBody: required: true content: application/json: schema: type: object required: [name, email, business_type] properties: name: type: string email: type: string format: email phone: type: string address: $ref: '#/components/schemas/Address' business_type: $ref: '#/components/schemas/BusinessType' responses: '201': description: Tenant created successfully content: application/json: schema: $ref: '#/components/schemas/Tenant' '400': description: Invalid input /tenants/{tenant_id}: get: summary: Get tenant details tags: [Tenants] parameters: - name: tenant_id in: path required: true type: string responses: '200': description: Tenant details content: application/json: schema: $ref: '#/components/schemas/Tenant' '404': description: Tenant not found put: summary: Update tenant tags: [Tenants] parameters: - name: tenant_id in: path required: true type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TenantUpdate' responses: '200': description: Tenant updated successfully content: application/json: schema: $ref: '#/components/schemas/Tenant' '404': description: Tenant not found # User management endpoints /users: get: summary: List users tags: [Users] parameters: - name: page in: query type: integer default: 1 - name: limit in: query type: integer default: 20 - name: role in: query type: string responses: '200': description: List of users content: application/json: schema: type: object properties: users: type: array items: $ref: '#/components/schemas/User' pagination: $ref: '#/components/schemas/Pagination' post: summary: Create new user tags: [Users] requestBody: required: true content: application/json: schema: type: object required: [email, first_name, last_name, role] properties: email: type: string format: email first_name: type: string last_name: type: string phone: type: string role: $ref: '#/components/schemas/UserRole' permissions: type: array items: type: string responses: '201': description: User created successfully content: application/json: schema: $ref: '#/components/schemas/User' '400': description: Invalid input /users/{user_id}: get: summary: Get user details tags: [Users] parameters: - name: user_id in: path required: true type: string responses: '200': description: User details content: application/json: schema: $ref: '#/components/schemas/User' '404': description: User not found put: summary: Update user tags: [Users] parameters: - name: user_id in: path required: true type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserUpdate' responses: '200': description: User updated successfully content: application/json: schema: $ref: '#/components/schemas/User' '404': description: User not found # Subscription management endpoints /subscriptions: get: summary: List subscriptions tags: [Subscriptions] parameters: - name: page in: query type: integer default: 1 - name: limit in: query type: integer default: 20 - name: status in: query type: string responses: '200': description: List of subscriptions content: application/json: schema: type: object properties: subscriptions: type: array items: $ref: '#/components/schemas/Subscription' pagination: $ref: '#/components/schemas/Pagination' post: summary: Create new subscription tags: [Subscriptions] requestBody: required: true content: application/json: schema: type: object required: [tenant_id, plan_type, billing_cycle] properties: tenant_id: type: string plan_type: $ref: '#/components/schemas/PlanType' billing_cycle: $ref: '#/components/schemas/BillingCycle' payment_method: type: string module_ids: type: array items: type: string responses: '201': description: Subscription created successfully content: application/json: schema: $ref: '#/components/schemas/Subscription' '400': description: Invalid input /subscriptions/{subscription_id}: get: summary: Get subscription details tags: [Subscriptions] parameters: - name: subscription_id in: path required: true type: string responses: '200': description: Subscription details content: application/json: schema: $ref: '#/components/schemas/Subscription' '404': description: Subscription not found put: summary: Update subscription tags: [Subscriptions] parameters: - name: subscription_id in: path required: true type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SubscriptionUpdate' responses: '200': description: Subscription updated successfully content: application/json: schema: $ref: '#/components/schemas/Subscription' '404': description: Subscription not found # Module management endpoints /modules: get: summary: List available modules tags: [Modules] parameters: - name: industry in: query type: string - name: page in: query type: integer default: 1 - name: limit in: query type: integer default: 20 responses: '200': description: List of modules content: application/json: schema: type: object properties: modules: type: array items: $ref: '#/components/schemas/Module' pagination: $ref: '#/components/schemas/Pagination' /modules/{module_id}: get: summary: Get module details tags: [Modules] parameters: - name: module_id in: path required: true type: string responses: '200': description: Module details content: application/json: schema: $ref: '#/components/schemas/Module' '404': description: Module not found # Payment endpoints /payments: post: summary: Process payment tags: [Payments] requestBody: required: true content: application/json: schema: type: object required: [subscription_id, amount, payment_method] properties: subscription_id: type: string amount: type: number format: decimal currency: type: string default: MYR payment_method: type: string description: type: string responses: '201': description: Payment processed successfully content: application/json: schema: $ref: '#/components/schemas/PaymentTransaction' '400': description: Invalid input '402': description: Payment failed # Retail module endpoints /retail/products: get: summary: List products (Retail module) tags: [Retail] parameters: - name: page in: query type: integer default: 1 - name: limit in: query type: integer default: 20 - name: category in: query type: string responses: '200': description: List of products content: application/json: schema: type: object properties: products: type: array items: $ref: '#/components/schemas/Product' pagination: $ref: '#/components/schemas/Pagination' post: summary: Create product (Retail module) tags: [Retail] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProductCreate' responses: '201': description: Product created successfully content: application/json: schema: $ref: '#/components/schemas/Product' '400': description: Invalid input /retail/sales: post: summary: Create sale (Retail module) tags: [Retail] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SaleCreate' responses: '201': description: Sale created successfully content: application/json: schema: $ref: '#/components/schemas/Sale' '400': description: Invalid input # Healthcare module endpoints /healthcare/patients: get: summary: List patients (Healthcare module) tags: [Healthcare] parameters: - name: page in: query type: integer default: 1 - name: limit in: query type: integer default: 20 - name: search in: query type: string responses: '200': description: List of patients content: application/json: schema: type: object properties: patients: type: array items: $ref: '#/components/schemas/Patient' pagination: $ref: '#/components/schemas/Pagination' post: summary: Create patient (Healthcare module) tags: [Healthcare] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PatientCreate' responses: '201': description: Patient created successfully content: application/json: schema: $ref: '#/components/schemas/Patient' '400': description: Invalid input /healthcare/appointments: get: summary: List appointments (Healthcare module) tags: [Healthcare] parameters: - name: page in: query type: integer default: 1 - name: limit in: query type: integer default: 20 - name: date in: query type: string format: date responses: '200': description: List of appointments content: application/json: schema: type: object properties: appointments: type: array items: $ref: '#/components/schemas/Appointment' pagination: $ref: '#/components/schemas/Pagination' post: summary: Create appointment (Healthcare module) tags: [Healthcare] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AppointmentCreate' responses: '201': description: Appointment created successfully content: application/json: schema: $ref: '#/components/schemas/Appointment' '400': description: Invalid input components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT schemas: # Core schemas Tenant: type: object properties: id: type: string format: uuid name: type: string slug: type: string email: type: string format: email phone: type: string address: $ref: '#/components/schemas/Address' business_type: $ref: '#/components/schemas/BusinessType' subscription_plan: $ref: '#/components/schemas/PlanType' pricing_model: $ref: '#/components/schemas/PricingModel' status: $ref: '#/components/schemas/TenantStatus' logo_url: type: string format: uri settings: type: object created_at: type: string format: date-time updated_at: type: string format: date-time User: type: object properties: id: type: string format: uuid tenant_id: type: string format: uuid email: type: string format: email first_name: type: string last_name: type: string phone: type: string role: $ref: '#/components/schemas/UserRole' status: $ref: '#/components/schemas/UserStatus' last_login: type: string format: date-time created_at: type: string format: date-time updated_at: type: string format: date-time mfa_enabled: type: boolean Subscription: type: object properties: id: type: string format: uuid tenant_id: type: string format: uuid plan_type: $ref: '#/components/schemas/PlanType' billing_cycle: $ref: '#/components/schemas/BillingCycle' status: $ref: '#/components/schemas/SubscriptionStatus' starts_at: type: string format: date-time ends_at: type: string format: date-time renews_at: type: string format: date-time amount: type: number format: decimal currency: type: string default: MYR payment_method: type: string module_limit: type: integer user_limit: type: integer features: type: object created_at: type: string format: date-time updated_at: type: string format: date-time Module: type: object properties: id: type: string format: uuid name: type: string slug: type: string description: type: string industry: $ref: '#/components/schemas/BusinessType' version: type: string status: $ref: '#/components/schemas/ModuleStatus' features: type: object requirements: type: object created_at: type: string format: date-time updated_at: type: string format: date-time PaymentTransaction: type: object properties: id: type: string format: uuid tenant_id: type: string format: uuid subscription_id: type: string format: uuid type: $ref: '#/components/schemas/PaymentType' amount: type: number format: decimal currency: type: string default: MYR status: $ref: '#/components/schemas/PaymentStatus' payment_method: type: string transaction_id: type: string description: type: string created_at: type: string format: date-time updated_at: type: string format: date-time # Retail module schemas Product: type: object properties: id: type: string format: uuid tenant_id: type: string format: uuid name: type: string sku: type: string description: type: string category: type: string price: type: number format: decimal cost: type: number format: decimal stock_quantity: type: integer reorder_point: type: integer supplier_id: type: string format: uuid status: $ref: '#/components/schemas/ProductStatus' created_at: type: string format: date-time updated_at: type: string format: date-time Sale: type: object properties: id: type: string format: uuid tenant_id: type: string format: uuid invoice_number: type: string customer_id: type: string format: uuid subtotal: type: number format: decimal tax: type: number format: decimal total: type: number format: decimal payment_method: type: string status: $ref: '#/components/schemas/SaleStatus' created_at: type: string format: date-time updated_at: type: string format: date-time # Healthcare module schemas Patient: type: object properties: id: type: string format: uuid tenant_id: type: string format: uuid medical_record_number: type: string first_name: type: string last_name: type: string ic_number: type: string date_of_birth: type: string format: date gender: $ref: '#/components/schemas/Gender' phone: type: string email: type: string format: email address: $ref: '#/components/schemas/Address' blood_type: type: string allergies: type: array items: type: string medical_conditions: type: array items: type: string created_at: type: string format: date-time updated_at: type: string format: date-time Appointment: type: object properties: id: type: string format: uuid tenant_id: type: string format: uuid patient_id: type: string format: uuid doctor_id: type: string format: uuid appointment_date: type: string format: date-time duration: type: integer status: $ref: '#/components/schemas/AppointmentStatus' type: $ref: '#/components/schemas/AppointmentType' notes: type: string reminder_sent: type: boolean created_at: type: string format: date-time updated_at: type: string format: date-time # Supporting schemas Address: type: object properties: street: type: string city: type: string state: type: string postal_code: type: string country: type: string default: Malaysia Pagination: type: object properties: page: type: integer limit: type: integer total: type: integer pages: type: integer # Enum schemas BusinessType: type: string enum: [RETAIL, HEALTHCARE, EDUCATION, LOGISTICS, BEAUTY] PlanType: type: string enum: [STARTER, GROWTH, PRO, ENTERPRISE] BillingCycle: type: string enum: [MONTHLY, YEARLY, ONE_TIME] PricingModel: type: string enum: [SUBSCRIPTION, PERPETUAL] TenantStatus: type: string enum: [PENDING, ACTIVE, SUSPENDED, TERMINATED] UserRole: type: string enum: [ADMIN, MANAGER, STAFF, VIEWER] UserStatus: type: string enum: [PENDING, ACTIVE, INACTIVE, DISABLED] SubscriptionStatus: type: string enum: [ACTIVE, CANCELLED, EXPIRED, PENDING] ModuleStatus: type: string enum: [ACTIVE, INACTIVE, BETA] PaymentType: type: string enum: [CHARGE, REFUND, CREDIT, ADJUSTMENT] PaymentStatus: type: string enum: [PENDING, COMPLETED, FAILED, REFUNDED] ProductStatus: type: string enum: [ACTIVE, INACTIVE, DISCONTINUED] SaleStatus: type: string enum: [PENDING, COMPLETED, REFUNDED] Gender: type: string enum: [MALE, FEMALE, OTHER] AppointmentStatus: type: string enum: [SCHEDULED, CONFIRMED, COMPLETED, CANCELLED, NO_SHOW] AppointmentType: type: string enum: [CONSULTATION, FOLLOW_UP, PROCEDURE] # Request schemas TenantUpdate: type: object properties: name: type: string phone: type: string address: $ref: '#/components/schemas/Address' logo_url: type: string format: uri settings: type: object UserUpdate: type: object properties: first_name: type: string last_name: type: string phone: type: string role: $ref: '#/components/schemas/UserRole' permissions: type: array items: type: string SubscriptionUpdate: type: object properties: plan_type: $ref: '#/components/schemas/PlanType' billing_cycle: $ref: '#/components/schemas/BillingCycle' payment_method: type: string module_ids: type: array items: type: string ProductCreate: type: object required: [name, sku, price, cost] properties: name: type: string sku: type: string description: type: string category: type: string price: type: number format: decimal cost: type: number format: decimal stock_quantity: type: integer reorder_point: type: integer supplier_id: type: string format: uuid SaleCreate: type: object required: [customer_id, items] properties: customer_id: type: string format: uuid items: type: array items: type: object properties: product_id: type: string format: uuid quantity: type: integer unit_price: type: number format: decimal payment_method: type: string PatientCreate: type: object required: [first_name, last_name, ic_number, date_of_birth] properties: first_name: type: string last_name: type: string ic_number: type: string date_of_birth: type: string format: date gender: $ref: '#/components/schemas/Gender' phone: type: string email: type: string format: email address: $ref: '#/components/schemas/Address' blood_type: type: string allergies: type: array items: type: string medical_conditions: type: array items: type: string AppointmentCreate: type: object required: [patient_id, doctor_id, appointment_date, duration] properties: patient_id: type: string format: uuid doctor_id: type: string format: uuid appointment_date: type: string format: date-time duration: type: integer type: $ref: '#/components/schemas/AppointmentType' notes: type: string # Error schemas Error: type: object properties: code: type: string message: type: string details: type: object timestamp: type: string format: date-time ValidationError: type: object properties: code: type: string default: VALIDATION_ERROR message: type: string errors: type: array items: type: object properties: field: type: string message: type: string UnauthorizedError: type: object properties: code: type: string default: UNAUTHORIZED message: type: string timestamp: type: string format: date-time NotFoundError: type: object properties: code: type: string default: NOT_FOUND message: type: string timestamp: type: string format: date-time