openapi: 3.1.0
info:
  title: SME SaaS Platform API
  version: 1.0.0
  summary: Tenant-aware Laravel API in front of ERPNext
  description: >
    Production-oriented OpenAPI document for the current Laravel backend surface.
    All JSON API responses follow a stable envelope:
    `success`, `message`, `data`, `error`, `meta`.
servers:
  - url: http://127.0.0.1:8000
    description: Local development
  - url: https://api.example.com
    description: Central API host example
tags:
  - name: Health
  - name: Auth
  - name: App
  - name: Catalog
  - name: Customers
  - name: Stock
  - name: Sales
  - name: Purchases
  - name: Expenses
  - name: Tenant Admin
  - name: Platform Admin
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Bearer
    TenantHeader:
      type: apiKey
      in: header
      name: X-Tenant-Id
  parameters:
    RequestIdHeader:
      name: X-Request-Id
      in: header
      required: false
      schema:
        type: string
      description: Optional correlation ID forwarded through logs and responses.
    TenantIdHeader:
      name: X-Tenant-Id
      in: header
      required: false
      schema:
        type: string
      description: >
        Tenant public ID or slug. Required on central API hosts when tenant cannot
        be resolved from hostname.
    TenantSlugHeader:
      name: X-Tenant-Slug
      in: header
      required: false
      schema:
        type: string
      description: Optional tenant slug fallback header on central API hosts.
    SessionPublicIdPath:
      name: sessionPublicId
      in: path
      required: true
      schema:
        type: string
    TenantSlugPath:
      name: tenant
      in: path
      required: true
      description: Tenant slug
      schema:
        type: string
    PerPageQuery:
      name: per_page
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
    LimitQuery:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 500
    PageQuery:
      name: page
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
    ProductSearchQuery:
      name: q
      in: query
      required: false
      schema:
        type: string
        maxLength: 255
      description: Search across item code, item name, and description.
    ProductIsActiveQuery:
      name: is_active
      in: query
      required: false
      schema:
        type: boolean
    ProductIsStockItemQuery:
      name: is_stock_item
      in: query
      required: false
      schema:
        type: boolean
    ProductSkuQuery:
      name: sku
      in: query
      required: false
      schema:
        type: string
        maxLength: 255
    ProductUomQuery:
      name: uom
      in: query
      required: false
      schema:
        type: string
        maxLength: 255
    ProductSortByQuery:
      name: sort_by
      in: query
      required: false
      schema:
        type: string
        enum: [item_code, item_name, modified, creation, standard_rate]
    ProductSortDirectionQuery:
      name: sort_direction
      in: query
      required: false
      schema:
        type: string
        enum: [asc, desc]
    SessionStatusQuery:
      name: status
      in: query
      required: false
      schema:
        type: string
        enum: [active, revoked, expired]
    DeviceStatusQuery:
      name: status
      in: query
      required: false
      schema:
        type: string
        enum: [active, revoked]
    DocumentStatusQuery:
      name: status
      in: query
      required: false
      schema:
        type: string
    UserEmailQuery:
      name: user_email
      in: query
      required: false
      schema:
        type: string
    DeviceUuidQuery:
      name: device_uuid
      in: query
      required: false
      schema:
        type: string
    AuditTenantIdQuery:
      name: tenant_id
      in: query
      required: false
      schema:
        type: string
    AuditUserIdQuery:
      name: user_id
      in: query
      required: false
      schema:
        type: string
    AuditEventTypeQuery:
      name: event_type
      in: query
      required: false
      schema:
        type: string
    CustomerGroupQuery:
      name: customer_group
      in: query
      required: false
      schema:
        type: string
    TerritoryQuery:
      name: territory
      in: query
      required: false
      schema:
        type: string
    SupplierGroupQuery:
      name: supplier_group
      in: query
      required: false
      schema:
        type: string
    CompanyQuery:
      name: company
      in: query
      required: false
      schema:
        type: string
    WarehouseQuery:
      name: warehouse
      in: query
      required: false
      schema:
        type: string
    InStockOnlyQuery:
      name: in_stock_only
      in: query
      required: false
      schema:
        type: boolean
    DateFromQuery:
      name: date_from
      in: query
      required: false
      schema:
        type: string
        format: date
    DateToQuery:
      name: date_to
      in: query
      required: false
      schema:
        type: string
        format: date
    CustomerIdQuery:
      name: customer_id
      in: query
      required: false
      schema:
        type: string
    SupplierIdQuery:
      name: supplier_id
      in: query
      required: false
      schema:
        type: string
    GenericSearchQuery:
      name: q
      in: query
      required: false
      schema:
        type: string
        maxLength: 255
    CustomerSortByQuery:
      name: sort_by
      in: query
      required: false
      schema:
        type: string
        enum: [customer_name, creation, modified]
    SupplierSortByQuery:
      name: sort_by
      in: query
      required: false
      schema:
        type: string
        enum: [supplier_name, creation, modified]
    StockSortByQuery:
      name: sort_by
      in: query
      required: false
      schema:
        type: string
        enum: [item_code, actual_qty, valuation_rate, modified]
    SalesSortByQuery:
      name: sort_by
      in: query
      required: false
      schema:
        type: string
        enum: [posting_date, modified, creation, grand_total]
    ExpenseSortByQuery:
      name: sort_by
      in: query
      required: false
      schema:
        type: string
        enum: [posting_date, modified, creation, total_debit]
    GenericSortDirectionQuery:
      name: sort_direction
      in: query
      required: false
      schema:
        type: string
        enum: [asc, desc]
  schemas:
    ApiMeta:
      type: object
      properties:
        request_id:
          type: string
          nullable: true
        timestamp:
          type: string
          format: date-time
        version:
          type: string
          nullable: true
        tenant:
          oneOf:
            - $ref: '#/components/schemas/TenantContext'
            - type: 'null'
    TenantContext:
      type: object
      properties:
        tenant:
          oneOf:
            - $ref: '#/components/schemas/TenantContextTenant'
            - type: 'null'
        source:
          type: string
          nullable: true
        requested_host:
          type: string
          nullable: true
        matched_domain:
          type: string
          nullable: true
        resolution_status:
          type: string
          nullable: true
        failure_reason:
          type: string
          nullable: true
    TenantContextTenant:
      type: object
      properties:
        id:
          type: string
        slug:
          type: string
        status:
          type: string
      required: [id, slug, status]
    ErrorObject:
      type: object
      properties:
        code:
          type: string
        details:
          type: object
          additionalProperties: true
      required: [code, details]
    HealthData:
      type: object
      properties:
        status:
          type: string
          example: ok
        app_name:
          type: string
        version:
          type: string
        environment:
          type: string
      required: [status, app_name, version, environment]
    ReadinessData:
      type: object
      properties:
        status:
          type: string
          enum: [ready, not_ready]
        checks:
          type: object
          additionalProperties:
            type: object
            properties:
              status:
                type: string
              message:
                type: string
                nullable: true
      required: [status, checks]
    AuthDevice:
      type: object
      properties:
        id:
          type: string
        device_uuid:
          type: string
        name:
          type: string
        platform:
          type: string
          nullable: true
        app_version:
          type: string
          nullable: true
        status:
          type: string
        is_verified:
          type: boolean
        last_seen_at:
          type: string
          format: date-time
          nullable: true
      required: [id, device_uuid, name, status, is_verified]
    AuthSession:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        last_used_at:
          type: string
          format: date-time
          nullable: true
        access_token_expires_at:
          type: string
          format: date-time
          nullable: true
        revoked_at:
          type: string
          format: date-time
          nullable: true
        device:
          oneOf:
            - $ref: '#/components/schemas/AuthDevice'
            - type: 'null'
      required: [id, status]
    TokenPair:
      type: object
      properties:
        token_type:
          type: string
          example: Bearer
        access_token:
          type: string
        refresh_token:
          type: string
        expires_in:
          type: integer
        session:
          $ref: '#/components/schemas/AuthSession'
      required: [token_type, access_token, refresh_token, expires_in, session]
    CurrentUserData:
      type: object
      properties:
        user:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
            email:
              type: string
              format: email
            status:
              type: string
            is_platform_admin:
              type: boolean
          required: [id, name, email, status, is_platform_admin]
        tenant:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
            slug:
              type: string
            status:
              type: string
          required: [id, name, slug, status]
        membership:
          type: object
          properties:
            id:
              type: string
            status:
              type: string
            is_owner:
              type: boolean
            roles:
              type: array
              items:
                type: string
            permissions:
              type: array
              items:
                type: string
          required: [id, status, is_owner, roles, permissions]
        current_session:
          $ref: '#/components/schemas/AuthSession'
        current_device:
          $ref: '#/components/schemas/AuthDevice'
      required: [user, tenant, membership, current_session, current_device]
    Product:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
        sku:
          type: string
          nullable: true
        name:
          type: string
        display_name:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        item_group:
          type: string
          nullable: true
        brand:
          type: string
          nullable: true
        image_url:
          type: string
          nullable: true
        is_stock_item:
          type: boolean
        is_sales_item:
          type: boolean
        is_purchase_item:
          type: boolean
        tracks_batch:
          type: boolean
        tracks_expiry:
          type: boolean
        uom:
          type: string
          nullable: true
        unit:
          type: string
          nullable: true
        standard_rate:
          type: number
          format: float
          nullable: true
        shelf_life_days:
          type: integer
          nullable: true
        expiry_date:
          type: string
          nullable: true
        is_active:
          type: boolean
    Option:
      type: object
      properties:
        value:
          type: string
        label:
          type: string
      required: [value, label]
    RetailBootstrap:
      type: object
      properties:
        features:
          type: object
          additionalProperties:
            type: boolean
        companies:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        warehouses:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        payment_modes:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        customer_groups:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        territories:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        expense_accounts:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        payment_accounts:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        cost_centers:
          type: array
          items:
            $ref: '#/components/schemas/Option'
        default_customer:
          oneOf:
            - $ref: '#/components/schemas/Customer'
            - type: 'null'
        currency:
          type: string
          nullable: true
      required:
        [features, companies, warehouses, payment_modes, customer_groups, territories, expense_accounts, payment_accounts, cost_centers, currency]
    Customer:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        display_name:
          type: string
          nullable: true
        mobile_no:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        customer_type:
          type: string
          nullable: true
        customer_group:
          type: string
          nullable: true
        territory:
          type: string
          nullable: true
        is_active:
          type: boolean
      required: [id, name, is_active]
    StockSummary:
      type: object
      properties:
        item_code:
          type: string
        item_name:
          type: string
          nullable: true
        warehouse:
          type: string
          nullable: true
        actual_qty:
          type: number
          format: float
        reserved_qty:
          type: number
          format: float
          nullable: true
        projected_qty:
          type: number
          format: float
          nullable: true
        valuation_rate:
          type: number
          format: float
          nullable: true
      required: [item_code, actual_qty]
    SalesInvoice:
      type: object
      properties:
        id:
          type: string
        customer_id:
          type: string
          nullable: true
        customer_name:
          type: string
        company:
          type: string
          nullable: true
        currency:
          type: string
          nullable: true
        grand_total:
          type: number
          format: float
          nullable: true
        outstanding_amount:
          type: number
          format: float
          nullable: true
        status:
          type: string
          nullable: true
        posting_date:
          type: string
          nullable: true
        posting_time:
          type: string
          nullable: true
        due_date:
          type: string
          nullable: true
        owner:
          type: string
          nullable: true
        remarks:
          type: string
          nullable: true
        warehouse:
          type: string
          nullable: true
        is_paid:
          type: boolean
        is_pos:
          type: boolean
        update_stock:
          type: boolean
        items_count:
          type: integer
        items:
          type: array
          items:
            $ref: '#/components/schemas/SalesInvoiceLine'
        payments:
          type: array
          items:
            $ref: '#/components/schemas/SalesInvoicePayment'
      required: [id, customer_name, is_paid, is_pos, update_stock, items_count]
    SalesInvoiceLine:
      type: object
      properties:
        item_code:
          type: string
        item_name:
          type: string
        description:
          type: string
          nullable: true
        qty:
          type: number
          format: float
          nullable: true
        uom:
          type: string
          nullable: true
        rate:
          type: number
          format: float
          nullable: true
        amount:
          type: number
          format: float
          nullable: true
        warehouse:
          type: string
          nullable: true
      required: [item_code, item_name]
    SalesInvoicePayment:
      type: object
      properties:
        mode_of_payment:
          type: string
        amount:
          type: number
          format: float
          nullable: true
      required: [mode_of_payment]
    Supplier:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        display_name:
          type: string
          nullable: true
        mobile_no:
          type: string
          nullable: true
        email:
          type: string
          nullable: true
        supplier_group:
          type: string
          nullable: true
        is_active:
          type: boolean
      required: [id, name, is_active]
    PurchaseInvoice:
      type: object
      properties:
        id:
          type: string
        supplier_id:
          type: string
          nullable: true
        supplier_name:
          type: string
        currency:
          type: string
          nullable: true
        grand_total:
          type: number
          format: float
          nullable: true
        status:
          type: string
          nullable: true
        posting_date:
          type: string
          nullable: true
        update_stock:
          type: boolean
        items_count:
          type: integer
      required: [id, supplier_name, update_stock, items_count]
    Expense:
      type: object
      properties:
        id:
          type: string
        company:
          type: string
          nullable: true
        posting_date:
          type: string
          nullable: true
        amount:
          type: number
          format: float
          nullable: true
        expense_account:
          type: string
          nullable: true
        payment_account:
          type: string
          nullable: true
        cost_center:
          type: string
          nullable: true
        remarks:
          type: string
          nullable: true
        status:
          type: string
          nullable: true
      required: [id]
    AdminTenant:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        slug:
          type: string
        status:
          type: string
        suspended_at:
          type: string
          format: date-time
          nullable: true
        overdue_at:
          type: string
          format: date-time
          nullable: true
        erpnext:
          type: object
          properties:
            site:
              type: string
            base_url:
              type: string
            timeout_seconds:
              type: integer
            is_active:
              type: boolean
            has_credentials:
              type: boolean
          required: [site, base_url, timeout_seconds, is_active, has_credentials]
        subscription:
          oneOf:
            - type: object
              properties:
                id:
                  type: integer
                status:
                  type: string
                plan_code:
                  type: string
                  nullable: true
                plan_name:
                  type: string
                  nullable: true
                current_period_ends_at:
                  type: string
                  format: date-time
                  nullable: true
            - type: 'null'
        domains:
          oneOf:
            - type: array
              items:
                type: object
                properties:
                  domain:
                    type: string
                  status:
                    type: string
                  is_primary:
                    type: boolean
                required: [domain, status, is_primary]
            - type: 'null'
      required: [id, name, slug, status, erpnext]
    AdminTenantSession:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        tenant_id:
          type: string
          nullable: true
        user:
          oneOf:
            - type: object
              properties:
                id:
                  type: string
                email:
                  type: string
                name:
                  type: string
              required: [id, email, name]
            - type: 'null'
        device:
          oneOf:
            - type: object
              properties:
                id:
                  type: string
                device_uuid:
                  type: string
                name:
                  type: string
                platform:
                  type: string
                  nullable: true
              required: [id, device_uuid, name]
            - type: 'null'
        ip_address:
          type: string
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        access_token_expires_at:
          type: string
          format: date-time
          nullable: true
        revoked_at:
          type: string
          format: date-time
          nullable: true
        revoked_reason:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true
      required: [id, status]
    AdminTenantDevice:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
        tenant_id:
          type: string
          nullable: true
        user:
          oneOf:
            - type: object
              properties:
                id:
                  type: string
                email:
                  type: string
                name:
                  type: string
              required: [id, email, name]
            - type: 'null'
        device_uuid:
          type: string
        name:
          type: string
        platform:
          type: string
          nullable: true
        app_version:
          type: string
          nullable: true
        is_verified:
          type: boolean
        verified_at:
          type: string
          format: date-time
          nullable: true
        last_seen_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true
      required: [id, status, device_uuid, name, is_verified]
    AdminAuditLog:
      type: object
      properties:
        id:
          type: string
        event_type:
          type: string
        actor_type:
          type: string
          nullable: true
        tenant_id:
          type: string
          nullable: true
        user_id:
          type: string
          nullable: true
        user_email:
          type: string
          nullable: true
        ip_address:
          type: string
          nullable: true
        payload:
          type: object
          additionalProperties: true
          nullable: true
        created_at:
          type: string
          format: date-time
          nullable: true
      required: [id, event_type]
    TenantUserCreateResult:
      type: object
      properties:
        user:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
            email:
              type: string
          required: [id, name, email]
        membership:
          type: object
          properties:
            id:
              type: string
            roles:
              type: array
              items:
                type: string
          required: [id, roles]
      required: [user, membership]
    RevokedCount:
      type: object
      properties:
        revoked_count:
          type: integer
      required: [revoked_count]
    SuccessEnvelope_Health:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/HealthData'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Readiness:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/ReadinessData'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_TokenPair:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/TokenPair'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_CurrentUser:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/CurrentUserData'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Sessions:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/AuthSession'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Devices:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/AuthDevice'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Null:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: 'null'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Products:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/Product'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
                filters:
                  $ref: '#/components/schemas/ProductListFilters'
      required: [success, message, data, error, meta]
    SuccessEnvelope_RetailBootstrap:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/RetailBootstrap'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Customers:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/Customer'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
                filters:
                  $ref: '#/components/schemas/CustomerListFilters'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Customer:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/Customer'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_StockSummary:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/StockSummary'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
                filters:
                  $ref: '#/components/schemas/StockListFilters'
      required: [success, message, data, error, meta]
    SuccessEnvelope_SalesInvoices:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/SalesInvoice'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
                filters:
                  $ref: '#/components/schemas/SalesInvoiceListFilters'
      required: [success, message, data, error, meta]
    SuccessEnvelope_SalesInvoice:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/SalesInvoice'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Suppliers:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/Supplier'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
                filters:
                  $ref: '#/components/schemas/SupplierListFilters'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Supplier:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/Supplier'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_PurchaseInvoices:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/PurchaseInvoice'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
                filters:
                  $ref: '#/components/schemas/PurchaseInvoiceListFilters'
      required: [success, message, data, error, meta]
    SuccessEnvelope_PurchaseInvoice:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/PurchaseInvoice'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Expenses:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/Expense'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
                filters:
                  $ref: '#/components/schemas/ExpenseListFilters'
      required: [success, message, data, error, meta]
    SuccessEnvelope_Expense:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/Expense'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_TenantUserCreate:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/TenantUserCreateResult'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_AdminTenant:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/AdminTenant'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_AdminTenantList:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/AdminTenant'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_AdminTenantSessions:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/AdminTenantSession'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_AdminTenantDevices:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/AdminTenantDevice'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_AdminAuditLogs:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          type: array
          items:
            $ref: '#/components/schemas/AdminAuditLog'
        error:
          type: 'null'
        meta:
          allOf:
            - $ref: '#/components/schemas/ApiMeta'
            - type: object
              properties:
                pagination:
                  $ref: '#/components/schemas/PaginationMeta'
      required: [success, message, data, error, meta]
    SuccessEnvelope_RevokedCount:
      type: object
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
        data:
          $ref: '#/components/schemas/RevokedCount'
        error:
          type: 'null'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    PaginationMeta:
      type: object
      properties:
        current_page:
          type: integer
        per_page:
          type: integer
        total:
          type: integer
        last_page:
          type: integer
        count:
          type: integer
      required: [current_page, per_page, total, last_page, count]
    ProductListFilters:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        search:
          type: string
          nullable: true
        is_active:
          type: boolean
          nullable: true
        is_stock_item:
          type: boolean
          nullable: true
        sku:
          type: string
          nullable: true
        uom:
          type: string
          nullable: true
        sort_by:
          type: string
        sort_direction:
          type: string
    CustomerListFilters:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        search:
          type: string
          nullable: true
        is_active:
          type: boolean
          nullable: true
        customer_group:
          type: string
          nullable: true
        territory:
          type: string
          nullable: true
        sort_by:
          type: string
        sort_direction:
          type: string
    StockListFilters:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        search:
          type: string
          nullable: true
        warehouse:
          type: string
          nullable: true
        in_stock_only:
          type: boolean
        sort_by:
          type: string
        sort_direction:
          type: string
    SalesInvoiceListFilters:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        search:
          type: string
          nullable: true
        status:
          type: string
          nullable: true
        customer_id:
          type: string
          nullable: true
        date_from:
          type: string
          nullable: true
        date_to:
          type: string
          nullable: true
        sort_by:
          type: string
        sort_direction:
          type: string
    SupplierListFilters:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        search:
          type: string
          nullable: true
        is_active:
          type: boolean
          nullable: true
        supplier_group:
          type: string
          nullable: true
        sort_by:
          type: string
        sort_direction:
          type: string
    PurchaseInvoiceListFilters:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        search:
          type: string
          nullable: true
        status:
          type: string
          nullable: true
        supplier_id:
          type: string
          nullable: true
        date_from:
          type: string
          nullable: true
        date_to:
          type: string
          nullable: true
        sort_by:
          type: string
        sort_direction:
          type: string
    ExpenseListFilters:
      type: object
      properties:
        page:
          type: integer
        per_page:
          type: integer
        search:
          type: string
          nullable: true
        company:
          type: string
          nullable: true
        date_from:
          type: string
          nullable: true
        date_to:
          type: string
          nullable: true
        sort_by:
          type: string
        sort_direction:
          type: string
    ErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: false
        message:
          type: string
        data:
          type: 'null'
        error:
          $ref: '#/components/schemas/ErrorObject'
        meta:
          $ref: '#/components/schemas/ApiMeta'
      required: [success, message, data, error, meta]
    LoginRequest:
      type: object
      properties:
        email:
          type: string
          format: email
        password:
          type: string
        device_uuid:
          type: string
        device_name:
          type: string
        platform:
          type: string
          nullable: true
        app_version:
          type: string
          nullable: true
        metadata:
          type: object
          additionalProperties: true
          nullable: true
      required: [email, password, device_uuid, device_name]
    RefreshTokenRequest:
      type: object
      properties:
        refresh_token:
          type: string
      required: [refresh_token]
    TenantUserCreateRequest:
      type: object
      properties:
        name:
          type: string
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8
        role:
          type: string
          nullable: true
      required: [name, email, password]
    CreateCustomerRequest:
      type: object
      properties:
        customer_name:
          type: string
        mobile_no:
          type: string
          nullable: true
        email_id:
          type: string
          format: email
          nullable: true
        customer_type:
          type: string
          nullable: true
        customer_group:
          type: string
          nullable: true
        territory:
          type: string
          nullable: true
      required: [customer_name]
    SalesInvoiceItemInput:
      type: object
      properties:
        item_code:
          type: string
        qty:
          type: number
          format: float
        rate:
          type: number
          format: float
          nullable: true
        uom:
          type: string
          nullable: true
        warehouse:
          type: string
          nullable: true
      required: [item_code, qty]
    SalesInvoicePaymentInput:
      type: object
      properties:
        mode_of_payment:
          type: string
        amount:
          type: number
          format: float
      required: [mode_of_payment, amount]
    CreateSalesInvoiceRequest:
      type: object
      properties:
        customer_id:
          type: string
        company:
          type: string
          nullable: true
        posting_date:
          type: string
          format: date
          nullable: true
        due_date:
          type: string
          format: date
          nullable: true
        currency:
          type: string
          nullable: true
        warehouse:
          type: string
          nullable: true
        remarks:
          type: string
          nullable: true
        is_pos:
          type: boolean
          nullable: true
        update_stock:
          type: boolean
          nullable: true
        items:
          type: array
          items:
            $ref: '#/components/schemas/SalesInvoiceItemInput'
        payments:
          type: array
          items:
            $ref: '#/components/schemas/SalesInvoicePaymentInput'
          nullable: true
      required: [customer_id, items]
    CreateSupplierRequest:
      type: object
      properties:
        supplier_name:
          type: string
        mobile_no:
          type: string
          nullable: true
        email_id:
          type: string
          format: email
          nullable: true
        supplier_group:
          type: string
          nullable: true
      required: [supplier_name]
    CreatePurchaseInvoiceRequest:
      type: object
      properties:
        supplier_id:
          type: string
        company:
          type: string
          nullable: true
        posting_date:
          type: string
          format: date
          nullable: true
        bill_date:
          type: string
          format: date
          nullable: true
        currency:
          type: string
          nullable: true
        warehouse:
          type: string
          nullable: true
        remarks:
          type: string
          nullable: true
        update_stock:
          type: boolean
          nullable: true
        items:
          type: array
          items:
            $ref: '#/components/schemas/SalesInvoiceItemInput'
      required: [supplier_id, items]
    CreateExpenseRequest:
      type: object
      properties:
        company:
          type: string
        posting_date:
          type: string
          format: date
        amount:
          type: number
          format: float
        expense_account:
          type: string
        payment_account:
          type: string
        cost_center:
          type: string
          nullable: true
        remarks:
          type: string
          nullable: true
      required: [company, posting_date, amount, expense_account, payment_account]
    CreateTenantRequest:
      type: object
      properties:
        name:
          type: string
        slug:
          type: string
        erpnext_site:
          type: string
        erpnext_base_url:
          type: string
          format: uri
        erpnext_api_key:
          type: string
          nullable: true
        erpnext_api_secret:
          type: string
          nullable: true
        erpnext_timeout_seconds:
          type: integer
          minimum: 1
          maximum: 120
        erpnext_is_active:
          type: boolean
        status:
          type: string
          enum: [active, suspended, archived, provisioning]
        plan_code:
          type: string
          nullable: true
        subscription_status:
          type: string
          enum: [active, trialing, past_due, suspended, cancelled, expired]
          nullable: true
        primary_domain:
          type: string
          nullable: true
        settings:
          type: object
          additionalProperties: true
          nullable: true
      required: [name, slug, erpnext_site, erpnext_base_url]
    UpdateTenantRequest:
      type: object
      properties:
        name:
          type: string
        slug:
          type: string
        primary_domain:
          type: string
          nullable: true
    UpdateTenantStatusRequest:
      type: object
      properties:
        status:
          type: string
          enum: [active, suspended, archived, provisioning]
        suspension_reason:
          type: string
          nullable: true
      required: [status]
    UpdateTenantSubscriptionRequest:
      type: object
      properties:
        plan_code:
          type: string
        status:
          type: string
          enum: [active, trialing, past_due, suspended, cancelled, expired]
          nullable: true
        auto_renew:
          type: boolean
      required: [plan_code]
    UpdateTenantSubscriptionStateRequest:
      type: object
      properties:
        status:
          type: string
          enum: [active, past_due]
        overdue_at:
          type: string
          format: date-time
          nullable: true
      required: [status]
    UpdateTenantERPNextConfigRequest:
      type: object
      properties:
        erpnext_site:
          type: string
        erpnext_base_url:
          type: string
          format: uri
        erpnext_api_key:
          type: string
          nullable: true
        erpnext_api_secret:
          type: string
          nullable: true
        erpnext_timeout_seconds:
          type: integer
          minimum: 1
          maximum: 120
        erpnext_is_active:
          type: boolean
    RotateTenantERPNextCredentialsRequest:
      type: object
      properties:
        erpnext_api_key:
          type: string
        erpnext_api_secret:
          type: string
      required: [erpnext_api_key, erpnext_api_secret]
    RevokeTenantSessionsRequest:
      type: object
      properties:
        session_ids:
          type: array
          items:
            type: string
        reason:
          type: string
          nullable: true
    RevokeTenantDevicesRequest:
      type: object
      properties:
        device_ids:
          type: array
          items:
            type: string
        reason:
          type: string
          nullable: true
  responses:
    BadRequest:
      description: Request validation or semantic failure
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Unauthorized:
      description: Missing or invalid bearer token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Forbidden:
      description: Permission or feature gate failure
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    NotFound:
      description: Resource or route not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    RateLimited:
      description: Request throttled
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    UpstreamFailure:
      description: ERPNext unavailable or upstream integration failure
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
paths:
  /api/v1/health:
    get:
      tags: [Health]
      summary: Liveness health check
      operationId: getHealth
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      responses:
        '200':
          description: Health payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Health'
  /api/v1/ready:
    get:
      tags: [Health]
      summary: Readiness health check
      operationId: getReadiness
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      responses:
        '200':
          description: App is ready
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Readiness'
        '503':
          description: App is not ready
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Readiness'
  /api/v1/auth/login:
    post:
      tags: [Auth]
      summary: Login with tenant-aware device registration
      operationId: postAuthLogin
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantIdHeader'
        - $ref: '#/components/parameters/TenantSlugHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Token pair issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_TokenPair'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/v1/auth/refresh:
    post:
      tags: [Auth]
      summary: Rotate refresh token and issue a new access token
      operationId: postAuthRefresh
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantIdHeader'
        - $ref: '#/components/parameters/TenantSlugHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshTokenRequest'
      responses:
        '200':
          description: Tokens refreshed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_TokenPair'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/v1/auth/me:
    get:
      tags: [Auth]
      summary: Get current authenticated actor
      operationId: getAuthMe
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      responses:
        '200':
          description: Current actor
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_CurrentUser'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/auth/logout:
    post:
      tags: [Auth]
      summary: Logout current session
      operationId: postAuthLogout
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      responses:
        '200':
          description: Session revoked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Null'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/auth/sessions:
    get:
      tags: [Auth]
      summary: List active sessions for current user within current tenant
      operationId: getAuthSessions
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      responses:
        '200':
          description: Sessions list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Sessions'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/auth/devices:
    get:
      tags: [Auth]
      summary: List active devices for current user within current tenant
      operationId: getAuthDevices
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      responses:
        '200':
          description: Devices list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Devices'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /api/v1/auth/sessions/{sessionPublicId}/revoke:
    post:
      tags: [Auth]
      summary: Revoke one of the current user's sessions
      operationId: postAuthSessionRevoke
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/SessionPublicIdPath'
      responses:
        '200':
          description: Session revoked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Null'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /api/v1/catalog/products:
    get:
      tags: [Catalog]
      summary: Fetch normalized ERP-backed product catalog
      operationId: getCatalogProducts
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - $ref: '#/components/parameters/ProductSearchQuery'
        - $ref: '#/components/parameters/ProductIsActiveQuery'
        - $ref: '#/components/parameters/ProductIsStockItemQuery'
        - $ref: '#/components/parameters/ProductSkuQuery'
        - $ref: '#/components/parameters/ProductUomQuery'
        - $ref: '#/components/parameters/ProductSortByQuery'
        - $ref: '#/components/parameters/ProductSortDirectionQuery'
      responses:
        '200':
          description: Product list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Products'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/app/bootstrap:
    get:
      tags: [App]
      summary: Fetch tenant-aware retail bootstrap metadata
      operationId: getAppBootstrap
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      responses:
        '200':
          description: Retail bootstrap metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_RetailBootstrap'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/customers:
    get:
      tags: [Customers]
      summary: List customers with search, filters, and pagination
      operationId: getCustomers
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/GenericSearchQuery'
        - $ref: '#/components/parameters/ProductIsActiveQuery'
        - $ref: '#/components/parameters/CustomerGroupQuery'
        - $ref: '#/components/parameters/TerritoryQuery'
        - $ref: '#/components/parameters/CustomerSortByQuery'
        - $ref: '#/components/parameters/GenericSortDirectionQuery'
      responses:
        '200':
          description: Customer list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Customers'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
    post:
      tags: [Customers]
      summary: Create customer in ERPNext through Laravel
      operationId: postCustomerCreate
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCustomerRequest'
      responses:
        '201':
          description: Customer created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Customer'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/stock/summary:
    get:
      tags: [Stock]
      summary: List stock summary with pagination and warehouse filters
      operationId: getStockSummary
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/GenericSearchQuery'
        - $ref: '#/components/parameters/WarehouseQuery'
        - $ref: '#/components/parameters/InStockOnlyQuery'
        - $ref: '#/components/parameters/StockSortByQuery'
        - $ref: '#/components/parameters/GenericSortDirectionQuery'
      responses:
        '200':
          description: Stock summary list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_StockSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/sales/invoices:
    get:
      tags: [Sales]
      summary: List sales invoices with pagination and date filters
      operationId: getSalesInvoices
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/GenericSearchQuery'
        - $ref: '#/components/parameters/DocumentStatusQuery'
        - $ref: '#/components/parameters/CustomerIdQuery'
        - $ref: '#/components/parameters/DateFromQuery'
        - $ref: '#/components/parameters/DateToQuery'
        - $ref: '#/components/parameters/SalesSortByQuery'
        - $ref: '#/components/parameters/GenericSortDirectionQuery'
      responses:
        '200':
          description: Sales invoice list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_SalesInvoices'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
    post:
      tags: [Sales]
      summary: Create sales invoice in ERPNext through Laravel
      operationId: postSalesInvoiceCreate
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSalesInvoiceRequest'
      responses:
        '201':
          description: Sales invoice created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_SalesInvoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/sales/invoices/{invoiceId}:
    get:
      tags: [Sales]
      summary: Get sales invoice detail with line items and payments
      operationId: getSalesInvoice
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - name: invoiceId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Sales invoice detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_SalesInvoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/suppliers:
    get:
      tags: [Purchases]
      summary: List suppliers with search, filters, and pagination
      operationId: getSuppliers
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/GenericSearchQuery'
        - $ref: '#/components/parameters/ProductIsActiveQuery'
        - $ref: '#/components/parameters/SupplierGroupQuery'
        - $ref: '#/components/parameters/SupplierSortByQuery'
        - $ref: '#/components/parameters/GenericSortDirectionQuery'
      responses:
        '200':
          description: Supplier list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Suppliers'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
    post:
      tags: [Purchases]
      summary: Create supplier in ERPNext through Laravel
      operationId: postSupplierCreate
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSupplierRequest'
      responses:
        '201':
          description: Supplier created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Supplier'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/purchases/invoices:
    get:
      tags: [Purchases]
      summary: List purchase invoices with pagination and date filters
      operationId: getPurchaseInvoices
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/GenericSearchQuery'
        - $ref: '#/components/parameters/DocumentStatusQuery'
        - $ref: '#/components/parameters/SupplierIdQuery'
        - $ref: '#/components/parameters/DateFromQuery'
        - $ref: '#/components/parameters/DateToQuery'
        - $ref: '#/components/parameters/SalesSortByQuery'
        - $ref: '#/components/parameters/GenericSortDirectionQuery'
      responses:
        '200':
          description: Purchase invoice list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_PurchaseInvoices'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
    post:
      tags: [Purchases]
      summary: Create purchase invoice in ERPNext through Laravel
      operationId: postPurchaseInvoiceCreate
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePurchaseInvoiceRequest'
      responses:
        '201':
          description: Purchase invoice created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_PurchaseInvoice'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/expenses:
    get:
      tags: [Expenses]
      summary: List expenses with pagination and accounting filters
      operationId: getExpenses
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PageQuery'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/GenericSearchQuery'
        - $ref: '#/components/parameters/CompanyQuery'
        - $ref: '#/components/parameters/DateFromQuery'
        - $ref: '#/components/parameters/DateToQuery'
        - $ref: '#/components/parameters/ExpenseSortByQuery'
        - $ref: '#/components/parameters/GenericSortDirectionQuery'
      responses:
        '200':
          description: Expense list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Expenses'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
    post:
      tags: [Expenses]
      summary: Create expense entry in ERPNext through Laravel
      operationId: postExpenseCreate
      security:
        - BearerAuth: []
          TenantHeader: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateExpenseRequest'
      responses:
        '201':
          description: Expense created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_Expense'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
        '502':
          $ref: '#/components/responses/UpstreamFailure'
        '503':
          $ref: '#/components/responses/UpstreamFailure'
  /api/v1/admin/tenant-users:
    post:
      tags: [Tenant Admin]
      summary: Create a tenant user and membership
      operationId: postTenantUserCreate
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TenantUserCreateRequest'
      responses:
        '201':
          description: Tenant user created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_TenantUserCreate'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants:
    get:
      tags: [Platform Admin]
      summary: List tenants
      operationId: getPlatformTenants
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PerPageQuery'
      responses:
        '200':
          description: Tenant list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenantList'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
    post:
      tags: [Platform Admin]
      summary: Create tenant
      operationId: postPlatformTenantCreate
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTenantRequest'
      responses:
        '201':
          description: Tenant created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants/{tenant}:
    get:
      tags: [Platform Admin]
      summary: Get tenant details
      operationId: getPlatformTenant
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      responses:
        '200':
          description: Tenant detail
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Platform Admin]
      summary: Update tenant metadata
      operationId: patchPlatformTenant
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTenantRequest'
      responses:
        '200':
          description: Tenant updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants/{tenant}/status:
    patch:
      tags: [Platform Admin]
      summary: Update tenant lifecycle status
      operationId: patchPlatformTenantStatus
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTenantStatusRequest'
      responses:
        '200':
          description: Tenant status updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants/{tenant}/subscription:
    patch:
      tags: [Platform Admin]
      summary: Assign or change tenant plan
      operationId: patchPlatformTenantSubscription
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTenantSubscriptionRequest'
      responses:
        '200':
          description: Subscription updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants/{tenant}/subscription/state:
    patch:
      tags: [Platform Admin]
      summary: Mark tenant subscription active or past due
      operationId: patchPlatformTenantSubscriptionState
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTenantSubscriptionStateRequest'
      responses:
        '200':
          description: Subscription state updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants/{tenant}/erpnext:
    patch:
      tags: [Platform Admin]
      summary: Update tenant ERPNext metadata
      operationId: patchPlatformTenantErpnext
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTenantERPNextConfigRequest'
      responses:
        '200':
          description: ERPNext config updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants/{tenant}/erpnext/rotate-credentials:
    post:
      tags: [Platform Admin]
      summary: Rotate ERPNext credentials
      operationId: postPlatformTenantRotateErpnextCredentials
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RotateTenantERPNextCredentialsRequest'
      responses:
        '200':
          description: ERPNext credentials rotated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenant'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/BadRequest'
  /api/v1/admin/platform/tenants/{tenant}/sessions:
    get:
      tags: [Platform Admin]
      summary: List tenant sessions
      operationId: getPlatformTenantSessions
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/SessionStatusQuery'
        - $ref: '#/components/parameters/UserEmailQuery'
        - $ref: '#/components/parameters/DeviceUuidQuery'
      responses:
        '200':
          description: Tenant sessions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenantSessions'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/admin/platform/tenants/{tenant}/sessions/revoke:
    post:
      tags: [Platform Admin]
      summary: Bulk revoke tenant sessions
      operationId: postPlatformTenantSessionsRevoke
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RevokeTenantSessionsRequest'
      responses:
        '200':
          description: Revoke result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_RevokedCount'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/admin/platform/tenants/{tenant}/devices:
    get:
      tags: [Platform Admin]
      summary: List tenant devices
      operationId: getPlatformTenantDevices
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/DeviceStatusQuery'
        - $ref: '#/components/parameters/UserEmailQuery'
        - $ref: '#/components/parameters/DeviceUuidQuery'
      responses:
        '200':
          description: Tenant devices
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminTenantDevices'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/admin/platform/tenants/{tenant}/devices/revoke:
    post:
      tags: [Platform Admin]
      summary: Bulk revoke tenant devices
      operationId: postPlatformTenantDevicesRevoke
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/TenantSlugPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RevokeTenantDevicesRequest'
      responses:
        '200':
          description: Revoke result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_RevokedCount'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /api/v1/admin/platform/audit-logs:
    get:
      tags: [Platform Admin]
      summary: List audit logs
      operationId: getPlatformAuditLogs
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/RequestIdHeader'
        - $ref: '#/components/parameters/PerPageQuery'
        - $ref: '#/components/parameters/AuditTenantIdQuery'
        - $ref: '#/components/parameters/AuditUserIdQuery'
        - $ref: '#/components/parameters/AuditEventTypeQuery'
      responses:
        '200':
          description: Audit logs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope_AdminAuditLogs'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
