openapi: 3.1.0
info:
  title: CrystalQore API
  version: "1.0.0"
  summary: REST API for CrystalQore integrations
  description: |
    Programmatic access to CrystalQore for CRM, reporting, and automation.

    **Base URL:** `https://app.crystalqore.com/api/v1`

    Authenticate every request with an API key and tenant ID. Some operations
    also require `X-User-Email` so the call runs as a specific user.

    This reference covers the customer integrator surface only.
  contact:
    name: CrystalQore
    url: https://docs.crystalqore.com/docs/developers
  license:
    name: Proprietary

servers:
  - url: https://app.crystalqore.com/api/v1
    description: Production

tags:
  - name: Users
    description: List people in a tenant
  - name: CDR
    description: Query call detail records
  - name: Contacts
    description: List and create contacts
  - name: Click to Dial
    description: Originate a call from a user's extension
  - name: Chat
    description: Conversations and messages
  - name: Files
    description: List files
  - name: Calendar
    description: List and create calendar events
  - name: Fax
    description: List inbound, outbound, and queued faxes
  - name: Presence
    description: Read tenant presence and update your own status

security:
  - ApiKey: []
    TenantId: []

paths:
  /users:
    get:
      tags: [Users]
      operationId: listUsers
      summary: List tenant users
      description: |
        Returns enabled users in the tenant. Search matches name, email, or
        extension. Set `includePresence=true` to attach a presence block.
      parameters:
        - $ref: "#/components/parameters/Search"
        - name: includePresence
          in: query
          description: When `true`, include presence for each user
          schema:
            type: boolean
            default: false
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: User list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsersResponse"
              example:
                success: true
                data:
                  users:
                    - id: 3f2c1a90-4b1e-4d2a-9c11-0a1b2c3d4e5f
                      firstName: Jordan
                      lastName: Lee
                      name: Jordan Lee
                      email: jordan@example.com
                      extension: "101"
                      phone: "+15551234567"
                      title: Account Executive
                      department: Sales
                      avatar: null
                      role: USER
                meta:
                  requestId: 8a1c2d3e-4f56-4789-a012-3456789abcde
                  timestamp: "2026-10-19T17:00:00.000Z"
                  pagination:
                    total: 12
                    limit: 50
                    offset: 0
                    hasMore: false
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /cdr:
    get:
      tags: [CDR]
      operationId: listCdr
      summary: Query call detail records
      description: |
        Returns call records for the tenant. If `X-User-Email` is sent, results
        are limited to that user's extensions and DID. The plan's call-record
        retention floor always applies; `startDate` cannot go older than that
        cutoff.

        When the phone system is not linked for the organization, the API
        returns `TENANT_NOT_CONFIGURED` or `PBX_NOT_CONFIGURED`.
      parameters:
        - name: startDate
          in: query
          description: Inclusive start (ISO 8601 date or datetime)
          schema:
            type: string
        - name: endDate
          in: query
          description: Inclusive end date (ISO 8601 date)
          schema:
            type: string
            format: date
        - name: extension
          in: query
          description: Filter by extension (ignored when `X-User-Email` is set)
          schema:
            type: string
        - name: direction
          in: query
          schema:
            type: string
            enum: [inbound, outbound, internal, all]
        - name: tz
          in: query
          description: IANA timezone for day boundaries (invalid values are ignored)
          schema:
            type: string
            example: America/New_York
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Call records
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CdrResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /contacts:
    get:
      tags: [Contacts]
      operationId: listContacts
      summary: List contacts
      description: |
        Lists contacts in the tenant. When `X-User-Email` is sent, results are
        limited to contacts owned by that user.
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [internal, external, all]
            default: all
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Contact list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags: [Contacts]
      operationId: createContact
      summary: Create a contact
      description: Creates a contact owned by the user in `X-User-Email`.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateContactRequest"
            example:
              firstName: Avery
              lastName: Chen
              company: Northwind
              phoneWork: "+15559870000"
              email: avery@northwind.example
      responses:
        "201":
          description: Contact created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactCreateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /click2dial:
    post:
      tags: [Click to Dial]
      operationId: click2dial
      summary: Originate a call
      description: |
        Rings the user's extension first, then bridges to `destination` when
        they answer. The user must have an extension and a registered phone.

        This mutates live telephony state. Use a real destination only when you
        intend to place the call.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Click2DialRequest"
            example:
              destination: "+15551234567"
      responses:
        "200":
          description: Call initiated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Click2DialResponse"
              example:
                success: true
                data:
                  callId: 9c8b7a65-4321-40fe-9abc-1234567890ab
                  status: initiated
                  from: "101"
                  to: "+15551234567"
                  message: Call initiated. Your phone will ring first.
                meta:
                  requestId: 8a1c2d3e-4f56-4789-a012-3456789abcde
                  timestamp: "2026-10-19T17:00:00.000Z"
        "400":
          $ref: "#/components/responses/BadRequest"
        "402":
          description: Subscription inactive
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          description: Phone not registered, or a call is already in progress
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /chat/conversations:
    get:
      tags: [Chat]
      operationId: listConversations
      summary: List conversations
      description: Lists conversations for the user in `X-User-Email`.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Conversation list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags: [Chat]
      operationId: createConversation
      summary: Start or reuse a direct conversation
      description: |
        Opens a direct conversation with `participantEmail` in the same tenant.
        If one already exists, that conversation is returned with `isNew: false`.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateConversationRequest"
            example:
              participantEmail: teammate@example.com
              message: Hello from the integration
      responses:
        "200":
          description: Existing conversation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationCreateResponse"
        "201":
          description: Conversation created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationCreateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          description: Participant not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /chat/messages:
    get:
      tags: [Chat]
      operationId: listMessages
      summary: List messages
      description: |
        Returns messages in a conversation the user belongs to. Reading a page
        updates the user's last-read timestamp. Use `before` for cursor paging.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      parameters:
        - name: conversationId
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: before
          in: query
          description: Return messages created before this timestamp (ISO 8601)
          schema:
            type: string
            format: date-time
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Message list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessagesResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags: [Chat]
      operationId: sendMessage
      summary: Send a message
      description: Sends a text message to a conversation the user belongs to.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMessageRequest"
            example:
              conversationId: 11111111-2222-4333-8444-555555555555
              content: Following up from the CRM
      responses:
        "201":
          description: Message sent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageCreateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /files:
    get:
      tags: [Files]
      operationId: listFiles
      summary: List files
      description: |
        Lists files in the tenant. Without `X-User-Email`, the list is
        tenant-wide. With a user, `filter` can limit to `my` or `shared`.
      parameters:
        - name: filter
          in: query
          schema:
            type: string
            enum: [all, my, shared]
            default: all
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: File list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FilesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /calendar:
    get:
      tags: [Calendar]
      operationId: listCalendarEvents
      summary: List calendar events
      description: Lists events owned by the user in `X-User-Email`.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      parameters:
        - name: startDate
          in: query
          description: Include events that end on or after this instant
          schema:
            type: string
        - name: endDate
          in: query
          description: Include events that start on or before this instant
          schema:
            type: string
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Event list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      tags: [Calendar]
      operationId: createCalendarEvent
      summary: Create a calendar event
      description: Creates an event on the calendar of the user in `X-User-Email`.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCalendarEventRequest"
            example:
              title: Customer kickoff
              startTime: "2026-10-20T14:00:00.000Z"
              endTime: "2026-10-20T15:00:00.000Z"
              location: Conference Room A
      responses:
        "201":
          description: Event created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CalendarCreateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /fax:
    get:
      tags: [Fax]
      operationId: listFaxes
      summary: List faxes
      description: |
        Lists faxes for the tenant from the phone system. `type` selects inbox
        (default), sent, or the outbound queue.
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum: [inbox, sent, queue]
            default: inbox
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          description: Fax list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FaxResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /presence:
    get:
      tags: [Presence]
      operationId: listPresence
      summary: List presence
      description: |
        Returns presence for enabled users in the tenant. Derived status can be
        `offline`, `dnd`, `on_phone`, `on_hold`, or the stored status.
      responses:
        "200":
          description: Presence list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PresenceListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    put:
      tags: [Presence]
      operationId: updatePresence
      summary: Update your presence
      description: Updates presence for the user in `X-User-Email` only.
      security:
        - ApiKey: []
          TenantId: []
          UserEmail: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePresenceRequest"
            example:
              status: busy
              message: In a customer call
      responses:
        "200":
          description: Presence updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PresenceUpdateResponse"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key issued by CrystalQore (`cq_` followed by 64 hex characters).
        You may instead send `Authorization: Bearer cq_…`.
    TenantId:
      type: apiKey
      in: header
      name: X-Tenant-Id
      description: Tenant UUID the key is allowed to access.
    UserEmail:
      type: apiKey
      in: header
      name: X-User-Email
      description: |
        Email of an enabled user in the tenant. Required for user-scoped writes
        and some reads (chat, calendar, click-to-dial).

  parameters:
    Limit:
      name: limit
      in: query
      description: Page size (default 50, maximum 100)
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    Offset:
      name: offset
      in: query
      description: Number of records to skip
      schema:
        type: integer
        minimum: 0
        default: 0
    Search:
      name: search
      in: query
      description: Free-text search (`q` is accepted as an alias)
      schema:
        type: string

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: UNAUTHORIZED
              message: Unauthorized
            meta:
              requestId: 8a1c2d3e-4f56-4789-a012-3456789abcde
              timestamp: "2026-10-19T17:00:00.000Z"
    Forbidden:
      description: Disabled client, tenant, user, missing permission, or entitlement
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ValidationError:
      description: Request body failed validation
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: VALIDATION_ERROR
              message: Validation failed
              details: []
            meta:
              requestId: 8a1c2d3e-4f56-4789-a012-3456789abcde
              timestamp: "2026-10-19T17:00:00.000Z"
    RateLimited:
      description: Client or origination rate limit exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: RATE_LIMIT_EXCEEDED
              message: Rate limit exceeded
            meta:
              requestId: 8a1c2d3e-4f56-4789-a012-3456789abcde
              timestamp: "2026-10-19T17:00:00.000Z"
    InternalError:
      description: Unexpected server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    Meta:
      type: object
      required: [requestId, timestamp]
      properties:
        requestId:
          type: string
          format: uuid
        timestamp:
          type: string
          format: date-time
        pagination:
          $ref: "#/components/schemas/Pagination"

    Pagination:
      type: object
      required: [total, limit, offset, hasMore]
      properties:
        total:
          type: integer
        limit:
          type: integer
        offset:
          type: integer
        hasMore:
          type: boolean

    ErrorObject:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
        details: {}

    ErrorResponse:
      type: object
      required: [success, error, meta]
      properties:
        success:
          type: boolean
          const: false
        error:
          $ref: "#/components/schemas/ErrorObject"
        meta:
          $ref: "#/components/schemas/Meta"

    UserPresence:
      type: object
      properties:
        status:
          type: string
        message:
          type: [string, "null"]
        lastSeen:
          type: [string, "null"]
          format: date-time

    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
        firstName:
          type: string
        lastName:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        extension:
          type: [string, "null"]
        phone:
          type: [string, "null"]
        title:
          type: [string, "null"]
        department:
          type: [string, "null"]
        avatar:
          type: [string, "null"]
        role:
          type: string
        presence:
          $ref: "#/components/schemas/UserPresence"

    UsersResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [users]
          properties:
            users:
              type: array
              items:
                $ref: "#/components/schemas/User"
        meta:
          $ref: "#/components/schemas/Meta"

    CdrRecord:
      type: object
      properties:
        id:
          type: string
        direction:
          type: string
        callerName:
          type: [string, "null"]
        callerNumber:
          type: [string, "null"]
        destination:
          type: [string, "null"]
        startTime:
          type: [string, "null"]
        answerTime:
          type: [string, "null"]
        endTime:
          type: [string, "null"]
        duration:
          type: integer
        billableSeconds:
          type: integer
        hangupCause:
          type: [string, "null"]
        isMissed:
          type: boolean
        hasRecording:
          type: boolean
        isClick2Dial:
          type: boolean
        click2DialFrom:
          type: [string, "null"]
        click2DialTo:
          type: [string, "null"]

    CdrResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [records]
          properties:
            records:
              type: array
              items:
                $ref: "#/components/schemas/CdrRecord"
        meta:
          $ref: "#/components/schemas/Meta"

    ContactPhones:
      type: object
      properties:
        work:
          type: [string, "null"]
        mobile:
          type: [string, "null"]
        home:
          type: [string, "null"]
        fax:
          type: [string, "null"]

    ContactEmails:
      type: object
      properties:
        primary:
          type: [string, "null"]
        secondary:
          type: [string, "null"]

    ContactAddress:
      type: object
      properties:
        street:
          type: [string, "null"]
        street2:
          type: [string, "null"]
        city:
          type: [string, "null"]
        state:
          type: [string, "null"]
        zipCode:
          type: [string, "null"]
        country:
          type: [string, "null"]

    Contact:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
        firstName:
          type: string
        lastName:
          type: string
        name:
          type: string
        company:
          type: [string, "null"]
        title:
          type: [string, "null"]
        phones:
          $ref: "#/components/schemas/ContactPhones"
        emails:
          $ref: "#/components/schemas/ContactEmails"
        address:
          $ref: "#/components/schemas/ContactAddress"
        website:
          type: [string, "null"]
        notes:
          type: [string, "null"]
        avatar:
          type: [string, "null"]
        owner:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ContactsResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [contacts]
          properties:
            contacts:
              type: array
              items:
                $ref: "#/components/schemas/Contact"
        meta:
          $ref: "#/components/schemas/Meta"

    CreateContactRequest:
      type: object
      required: [firstName]
      properties:
        firstName:
          type: string
          minLength: 1
          maxLength: 100
        lastName:
          type: string
          maxLength: 100
        company:
          type: string
          maxLength: 200
        title:
          type: string
          maxLength: 100
        phoneWork:
          type: string
          maxLength: 50
        phoneMobile:
          type: string
          maxLength: 50
        phoneHome:
          type: string
          maxLength: 50
        phoneFax:
          type: string
          maxLength: 50
        email:
          type: string
        emailSecondary:
          type: string
        address:
          type: string
          maxLength: 200
        address2:
          type: string
          maxLength: 200
        city:
          type: string
          maxLength: 100
        state:
          type: string
          maxLength: 100
        zipCode:
          type: string
          maxLength: 20
        country:
          type: string
          maxLength: 100
        website:
          type: string
        notes:
          type: string
          maxLength: 5000

    ContactCreateResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [contact]
          properties:
            contact:
              $ref: "#/components/schemas/Contact"
        meta:
          $ref: "#/components/schemas/Meta"

    Click2DialRequest:
      type: object
      required: [destination]
      properties:
        destination:
          type: string
          minLength: 2
          maxLength: 50
          description: Number or extension to call after the user answers
        ringback:
          type: string
          maxLength: 128
          description: Optional custom ringback tone
        sipContact:
          type: string
          maxLength: 512
          description: Optional SIP contact for a specific registered device

    Click2DialResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            callId:
              type: string
            status:
              type: string
              example: initiated
            from:
              type: string
            to:
              type: string
            message:
              type: string
        meta:
          $ref: "#/components/schemas/Meta"

    ChatParticipant:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: [string, "null"]
        avatar:
          type: [string, "null"]
        isAdmin:
          type: boolean

    Conversation:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
        name:
          type: [string, "null"]
        avatar:
          type: [string, "null"]
        description:
          type: [string, "null"]
        participants:
          type: array
          items:
            $ref: "#/components/schemas/ChatParticipant"
        lastMessage:
          type: [object, "null"]
          properties:
            id:
              type: string
            content:
              type: string
            senderName:
              type: [string, "null"]
            timestamp:
              type: string
              format: date-time
        unreadCount:
          type: integer
        muted:
          type: boolean
        joinedAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ConversationsResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [conversations]
          properties:
            conversations:
              type: array
              items:
                $ref: "#/components/schemas/Conversation"
        meta:
          $ref: "#/components/schemas/Meta"

    CreateConversationRequest:
      type: object
      required: [participantEmail]
      properties:
        participantEmail:
          type: string
          format: email
        message:
          type: string
          minLength: 1
          maxLength: 5000

    ConversationCreateResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [conversation]
          properties:
            conversation:
              type: object
              properties:
                id:
                  type: string
                type:
                  type: string
                name:
                  type: string
                participants:
                  type: array
                  items:
                    $ref: "#/components/schemas/ChatParticipant"
                isNew:
                  type: boolean
        meta:
          $ref: "#/components/schemas/Meta"

    ChatMessage:
      type: object
      properties:
        id:
          type: string
        type:
          type: string
        content:
          type: string
        sender:
          type: object
          properties:
            id:
              type: [string, "null"]
            name:
              type: string
            email:
              type: [string, "null"]
            avatar:
              type: [string, "null"]
        file:
          type: [object, "null"]
          properties:
            id:
              type: string
            name:
              type: string
            mimeType:
              type: string
            size:
              type: integer
        isEdited:
          type: boolean
        timestamp:
          type: string
          format: date-time
        isOwn:
          type: boolean

    MessagesResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [messages]
          properties:
            messages:
              type: array
              items:
                $ref: "#/components/schemas/ChatMessage"
        meta:
          $ref: "#/components/schemas/Meta"

    SendMessageRequest:
      type: object
      required: [conversationId, content]
      properties:
        conversationId:
          type: string
          format: uuid
        content:
          type: string
          minLength: 1
          maxLength: 5000

    MessageCreateResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [message]
          properties:
            message:
              $ref: "#/components/schemas/ChatMessage"
        meta:
          $ref: "#/components/schemas/Meta"

    FileItem:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        filename:
          type: string
        mimeType:
          type: string
        size:
          type: integer
        source:
          type: string
        sharedWithTenant:
          type: boolean
        owner:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        transferredFromName:
          type: [string, "null"]
        transferredAt:
          type: [string, "null"]
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    FilesResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [files]
          properties:
            files:
              type: array
              items:
                $ref: "#/components/schemas/FileItem"
        meta:
          $ref: "#/components/schemas/Meta"

    CalendarEvent:
      type: object
      properties:
        id:
          type: string
        title:
          type: string
        description:
          type: [string, "null"]
        location:
          type: [string, "null"]
        startTime:
          type: string
          format: date-time
        endTime:
          type: string
          format: date-time
        allDay:
          type: boolean
        color:
          type: [string, "null"]
        isRecurring:
          type: boolean
        recurrenceRule:
          type: [string, "null"]
        reminderMinutes:
          type: [integer, "null"]
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    CalendarListResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [events]
          properties:
            events:
              type: array
              items:
                $ref: "#/components/schemas/CalendarEvent"
        meta:
          $ref: "#/components/schemas/Meta"

    CreateCalendarEventRequest:
      type: object
      required: [title, startTime, endTime]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
          maxLength: 5000
        location:
          type: string
          maxLength: 500
        startTime:
          type: string
          format: date-time
        endTime:
          type: string
          format: date-time
        allDay:
          type: boolean
          default: false
        color:
          type: string
          maxLength: 20
        reminderMinutes:
          type: integer
          minimum: 0
          maximum: 10080

    CalendarCreateResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [event]
          properties:
            event:
              $ref: "#/components/schemas/CalendarEvent"
        meta:
          $ref: "#/components/schemas/Meta"

    FaxItem:
      type: object
      properties:
        id:
          type: string
        date:
          type: [string, "null"]
        callerName:
          type: [string, "null"]
        callerNumber:
          type: [string, "null"]
        destination:
          type: [string, "null"]
        pages:
          type: [integer, "null"]
        status:
          type: string
        retryCount:
          type: [integer, "null"]
        sender:
          type: [string, "null"]

    FaxResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [faxes, type]
          properties:
            faxes:
              type: array
              items:
                $ref: "#/components/schemas/FaxItem"
            type:
              type: string
        meta:
          $ref: "#/components/schemas/Meta"

    PresenceItem:
      type: object
      properties:
        userId:
          type: string
        name:
          type: string
        email:
          type: string
        extension:
          type: [string, "null"]
        status:
          type: string
        message:
          type: [string, "null"]
        onPhone:
          type: boolean
        onHold:
          type: boolean
        lastSeen:
          type: [string, "null"]
          format: date-time

    PresenceListResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [presence]
          properties:
            presence:
              type: array
              items:
                $ref: "#/components/schemas/PresenceItem"
        meta:
          $ref: "#/components/schemas/Meta"

    UpdatePresenceRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [available, busy, away, dnd]
        message:
          type: string
          maxLength: 200

    PresenceUpdateResponse:
      type: object
      required: [success, data, meta]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            userId:
              type: string
            status:
              type: string
            message:
              type: [string, "null"]
            updatedAt:
              type: string
              format: date-time
        meta:
          $ref: "#/components/schemas/Meta"
