openapi: 3.0.3
info:
  title: IIOS — Insignia Interaction OS API
  version: "0.9.0"
  description: >
    REST API for the IIOS service. Everything is an Interaction inside a tenant scope,
    behind auth/consent/policy gates. Auth is a per-app Bearer JWT (mint dev tokens via
    POST /v1/dev/token when IIOS_DEV_TOKENS=1). Cross-tenant access returns 403.
    Companion docs: docs/IIOS_API_AND_SDK_GUIDE.md.
servers:
  - url: http://localhost:3200
    description: Local dev

security:
  - bearerAuth: []

tags:
  - name: Health
  - name: Dev
  - name: Interactions
  - name: Threads
  - name: Inbox
  - name: Support
  - name: Adapters
  - name: Routing
  - name: AI
  - name: Calendar

paths:
  /health:
    get:
      tags: [Health]
      summary: Liveness + egress provider bindings
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
                  db: { type: boolean }
                  egressProviders:
                    type: object
                    additionalProperties: { type: string }
                    example: { WEBHOOK: sandbox, EMAIL: http }

  /v1/dev/token:
    post:
      tags: [Dev]
      summary: Mint a dev JWT (requires IIOS_DEV_TOKENS=1)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [appId, userId]
              properties:
                appId: { type: string, example: portal-demo }
                userId: { type: string, example: alice }
                name: { type: string }
                orgId: { type: string, description: "Tenant org; default org_<appId>", example: org_A }
      responses:
        "201":
          description: Token
          content:
            application/json:
              schema:
                type: object
                properties: { token: { type: string } }
        "400": { $ref: "#/components/responses/BadRequest" }

  /v1/dev/webhook/{channelType}:
    post:
      tags: [Dev]
      summary: Inject a server-signed simulated inbound webhook (dev only)
      security: []
      parameters:
        - $ref: "#/components/parameters/channelType"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                from: { type: string }
                text: { type: string }
      responses:
        "201": { description: Raw inbound event accepted }

  /v1/interactions/ingest:
    post:
      tags: [Interactions]
      summary: Ingest a normalized interaction into the kernel (idempotent by providerEventId)
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/IngestRequest" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties: { interactionId: { type: string } }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }

  /v1/threads/{id}/messages:
    get:
      tags: [Threads]
      summary: List a thread's messages
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  threadId: { type: string }
                  messages: { type: array, items: { type: object } }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [Threads]
      summary: Send a message (REST fallback for the socket path)
      parameters:
        - $ref: "#/components/parameters/idPath"
        - name: idempotency-key
          in: header
          required: false
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content: { type: string }
                contentRef: { type: string }
      responses:
        "201":
          description: Message
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Message" }
        "400": { $ref: "#/components/responses/BadRequest" }

  /v1/inbox/items:
    get:
      tags: [Inbox]
      summary: List inbox items for the caller
      parameters:
        - name: state
          in: query
          required: false
          schema: { $ref: "#/components/schemas/InboxState" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { type: array, items: { $ref: "#/components/schemas/InboxItem" } }

  /v1/inbox/items/{id}:
    patch:
      tags: [Inbox]
      summary: Transition an inbox item
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [state]
              properties:
                state: { $ref: "#/components/schemas/InboxState" }
                reason: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: "#/components/schemas/InboxItem" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/support/tickets:
    post:
      tags: [Support]
      summary: Create a ticket
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subject]
              properties:
                subject: { type: string }
                priority: { $ref: "#/components/schemas/TicketPriority" }
                threadId: { type: string }
      responses:
        "201": { description: Ticket, content: { application/json: { schema: { $ref: "#/components/schemas/Ticket" } } } }
    get:
      tags: [Support]
      summary: List tickets
      parameters:
        - name: scope
          in: query
          required: false
          schema: { type: string, enum: [mine, assigned], default: mine }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Ticket" } } } } }

  /v1/support/escalate:
    post:
      tags: [Support]
      summary: Escalate a thread to support (opens a customer↔agent thread)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [threadId]
              properties:
                threadId: { type: string }
                subject: { type: string }
      responses:
        "201": { description: Ticket, content: { application/json: { schema: { $ref: "#/components/schemas/Ticket" } } } }

  /v1/support/tickets/{id}:
    patch:
      tags: [Support]
      summary: Transition a ticket
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [state]
              properties:
                state: { $ref: "#/components/schemas/TicketState" }
                reason: { type: string }
      responses:
        "200": { description: Ticket, content: { application/json: { schema: { $ref: "#/components/schemas/Ticket" } } } }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/support/callbacks:
    post:
      tags: [Support]
      summary: Request a callback (bridges to a meeting)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [preferChannel]
              properties:
                preferChannel: { type: string, example: PHONE }
                preferredTime: { type: string }
                notes: { type: string }
                ticketId: { type: string }
      responses:
        "201": { description: CallbackRequest, content: { application/json: { schema: { type: object } } } }

  /v1/support/queues:
    post:
      tags: [Support]
      summary: Create a support queue
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [name], properties: { name: { type: string } } }
      responses:
        "201": { description: Queue, content: { application/json: { schema: { type: object, properties: { id: { type: string } } } } } }

  /v1/support/queues/{id}/members:
    post:
      tags: [Support]
      summary: Add the caller to a queue
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: Membership }

  /v1/support/agents/me/join:
    post:
      tags: [Support]
      summary: Join the default queue (go online)
      responses:
        "201": { description: Membership }

  /v1/support/members/me/availability:
    patch:
      tags: [Support]
      summary: Set the caller's availability
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [state], properties: { state: { type: string, example: AVAILABLE } } }
      responses:
        "200": { description: Membership }

  /v1/adapters/{channelType}/webhook:
    post:
      tags: [Adapters]
      summary: Provider inbound webhook (HMAC-signed; async normalize → 202)
      security: []
      parameters:
        - $ref: "#/components/parameters/channelType"
        - name: x-iios-signature
          in: header
          required: true
          schema: { type: string, example: "sha256=..." }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, description: raw provider payload }
      responses:
        "202": { description: Accepted (normalized asynchronously) }
        "401": { description: Bad signature }

  /v1/adapters/{channelType}/send:
    post:
      tags: [Adapters]
      summary: Outbound send (through the Capability Broker; sandbox by default)
      parameters: [{ $ref: "#/components/parameters/channelType" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [target]
              properties:
                target: { type: string }
                payload: { type: object }
                idempotencyKey: { type: string }
      responses:
        "201": { description: OutboundCommand, content: { application/json: { schema: { $ref: "#/components/schemas/OutboundCommand" } } } }

  /v1/adapters/inbound:
    get:
      tags: [Adapters]
      summary: List raw inbound events (+ interactionId once normalized)
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { type: object } } } } }

  /v1/adapters/outbound:
    get:
      tags: [Adapters]
      summary: List outbound commands (+ delivery attempts)
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/OutboundCommand" } } } } }

  /v1/routes/bindings:
    post:
      tags: [Routing]
      summary: Create a route binding
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateBinding" }
      responses:
        "201": { description: RouteBinding, content: { application/json: { schema: { $ref: "#/components/schemas/RouteBinding" } } } }
    get:
      tags: [Routing]
      summary: List bindings (your scope)
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RouteBinding" } } } } }

  /v1/routes/simulate:
    post:
      tags: [Routing]
      summary: Preview-first simulation (produces decisions, sends nothing)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [interactionId, originChannelType]
              properties:
                interactionId: { type: string }
                originChannelType: { type: string }
                originRef: { type: string }
      responses:
        "201":
          description: Simulation
          content:
            application/json:
              schema:
                type: object
                properties:
                  simulationId: { type: string }
                  decisions: { type: array, items: { $ref: "#/components/schemas/RouteDecision" } }

  /v1/routes/decisions:
    get:
      tags: [Routing]
      summary: List route decisions (your scope)
      parameters:
        - name: state
          in: query
          required: false
          schema: { $ref: "#/components/schemas/RouteDecisionState" }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/RouteDecision" } } } } }

  /v1/routes/decisions/{id}/approve:
    post:
      tags: [Routing]
      summary: Approve a REVIEW decision (executes to sandbox)
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: RouteDecision, content: { application/json: { schema: { $ref: "#/components/schemas/RouteDecision" } } } }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/routes/decisions/{id}/deny:
    post:
      tags: [Routing]
      summary: Deny a decision
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: RouteDecision, content: { application/json: { schema: { $ref: "#/components/schemas/RouteDecision" } } } }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/routes/bindings/{id}/flush-digest:
    post:
      tags: [Routing]
      summary: Flush a binding's pending DIGEST decisions into one forward
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: OK, content: { application/json: { schema: { type: object, properties: { forwarded: { type: integer } } } } } }

  /v1/ai/jobs:
    post:
      tags: [AI]
      summary: Run an AI job (proposes; never decides)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [interactionId, jobType]
              properties:
                interactionId: { type: string }
                jobType: { $ref: "#/components/schemas/AiJobType" }
      responses:
        "201":
          description: Job + proposed artifact
          content:
            application/json:
              schema:
                type: object
                properties:
                  job: { type: object }
                  artifact: { $ref: "#/components/schemas/AiArtifact" }

  /v1/ai/artifacts:
    get:
      tags: [AI]
      summary: List AI artifacts (your scope)
      parameters:
        - name: interactionId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { $ref: "#/components/schemas/AiArtifactStatus" }
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/AiArtifact" } } } } }

  /v1/ai/artifacts/{id}:
    get:
      tags: [AI]
      summary: Get an artifact (claims + evidence + modelRun)
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "200": { description: AiArtifact, content: { application/json: { schema: { $ref: "#/components/schemas/AiArtifact" } } } }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/ai/artifacts/{id}/accept:
    post:
      tags: [AI]
      summary: Accept an artifact (human feedback)
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: AiArtifact, content: { application/json: { schema: { $ref: "#/components/schemas/AiArtifact" } } } }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/ai/artifacts/{id}/reject:
    post:
      tags: [AI]
      summary: Reject an artifact
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: AiArtifact, content: { application/json: { schema: { $ref: "#/components/schemas/AiArtifact" } } } }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/calendar/meetings:
    post:
      tags: [Calendar]
      summary: Schedule a meeting directly
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ScheduleMeeting" }
      responses:
        "201": { description: Meeting, content: { application/json: { schema: { $ref: "#/components/schemas/Meeting" } } } }
    get:
      tags: [Calendar]
      summary: List meetings (your scope)
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { $ref: "#/components/schemas/Meeting" } } } } }

  /v1/calendar/requests:
    post:
      tags: [Calendar]
      summary: Create a meeting request (from a callback / AI claim / direct)
      parameters:
        - name: fromCallback
          in: query
          schema: { type: string }
        - name: fromClaim
          in: query
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [requestedWindow]
              properties:
                requestedWindow: { type: object }
                meetingType: { $ref: "#/components/schemas/MeetingType" }
                interactionId: { type: string }
                targetActorId: { type: string }
      responses:
        "201": { description: MeetingRequest, content: { application/json: { schema: { type: object } } } }

  /v1/calendar/meetings/{id}:
    get:
      tags: [Calendar]
      summary: Get a meeting (participants, transcripts, summaries, action items)
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "200": { description: Meeting, content: { application/json: { schema: { $ref: "#/components/schemas/Meeting" } } } }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }

  /v1/calendar/meetings/{id}/consent:
    post:
      tags: [Calendar]
      summary: Set a participant's recording consent
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [actorRefId, status]
              properties:
                actorRefId: { type: string }
                status: { $ref: "#/components/schemas/ConsentStatus" }
      responses:
        "201": { description: Participant }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/calendar/meetings/{id}/transcript:
    post:
      tags: [Calendar]
      summary: Generate a transcript (BLOCKED unless every attendee consented)
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201":
          description: Transcript (status READY or BLOCKED)
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, enum: [READY, BLOCKED, PENDING] }
                  segments: { type: array, items: { type: object } }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/calendar/meetings/{id}/summarize:
    post:
      tags: [Calendar]
      summary: Summarize a READY transcript (AI) + derive action items
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: Meeting, content: { application/json: { schema: { $ref: "#/components/schemas/Meeting" } } } }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/calendar/meetings/{id}/action-items:
    get:
      tags: [Calendar]
      summary: List meeting action items
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: array, items: { type: object } } } } }
        "403": { $ref: "#/components/responses/Forbidden" }

  /v1/calendar/providers:
    post:
      tags: [Calendar]
      summary: Connect a simulated calendar provider account
      responses:
        "201": { description: ProviderAccount, content: { application/json: { schema: { type: object, properties: { id: { type: string } } } } } }

  /v1/calendar/providers/{id}/sync:
    post:
      tags: [Calendar]
      summary: Sync the provider once (deterministic, no network)
      parameters: [{ $ref: "#/components/parameters/idPath" }]
      responses:
        "201": { description: OK, content: { application/json: { schema: { type: object, properties: { pulled: { type: integer }, cursor: { type: string } } } } } }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  parameters:
    idPath:
      name: id
      in: path
      required: true
      schema: { type: string }
    channelType:
      name: channelType
      in: path
      required: true
      schema: { type: string, enum: [WEBHOOK, EMAIL, WHATSAPP, PORTAL] }

  responses:
    BadRequest:
      description: Validation error / missing auth / unknown field
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Unauthorized:
      description: Invalid or missing token / bad signature
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Forbidden:
      description: Cross-tenant access — resource not in your scope
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    NotFound:
      description: Not found (within your scope)
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }

  schemas:
    Error:
      type: object
      properties:
        statusCode: { type: integer }
        message: { type: string }
        error: { type: string }

    InboxState: { type: string, enum: [OPEN, SNOOZED, DONE, ARCHIVED, CANCELLED, STALE] }
    TicketState: { type: string, enum: [NEW, OPEN, PENDING, RESOLVED, CLOSED] }
    TicketPriority: { type: string, enum: [LOW, NORMAL, HIGH, URGENT] }
    RouteMode: { type: string, enum: [MANUAL, AUTOMATIC, HYBRID, SIMULATION_ONLY] }
    OutputFormat: { type: string, enum: [FORWARD, THREADED, DIGEST, SUMMARY, TRANSCRIPT] }
    RouteDecisionState: { type: string, enum: [ALLOW, DENY, REVIEW, SUPPRESS, SIMULATED] }
    AiJobType: { type: string, enum: [CLASSIFY, SUMMARIZE, EXTRACT] }
    AiArtifactStatus: { type: string, enum: [PROPOSED, ACCEPTED, REJECTED, SUPERSEDED] }
    MeetingType: { type: string, enum: [ZOOM, PHONE, IN_PERSON, CALLBACK, INTERNAL] }
    ConsentStatus: { type: string, enum: [UNKNOWN, GRANTED, DENIED, REVOKED] }

    IngestRequest:
      type: object
      required: [scope, channel, source, parts, occurredAt]
      properties:
        scope:
          type: object
          required: [orgId, appId]
          properties:
            orgId: { type: string }
            appId: { type: string }
            buId: { type: string }
            tenantId: { type: string }
            workspaceId: { type: string }
            projectId: { type: string }
            userScopeId: { type: string }
        channel:
          type: object
          required: [type]
          properties:
            type: { type: string }
            externalChannelId: { type: string }
            capabilityContract: { type: object }
        source:
          type: object
          required: [handleKind, externalId]
          properties:
            handleKind: { type: string }
            externalId: { type: string }
            displayName: { type: string }
        kind: { type: string }
        thread:
          type: object
          properties:
            externalThreadId: { type: string }
            subject: { type: string }
        parts:
          type: array
          items:
            type: object
            required: [kind]
            properties:
              kind: { type: string, example: TEXT }
              bodyText: { type: string }
              contentRef: { type: string }
              mimeType: { type: string }
        occurredAt: { type: string, format: date-time }
        providerEventId: { type: string }
        metadata: { type: object }

    Message:
      type: object
      properties:
        id: { type: string }
        threadId: { type: string }
        senderActorId: { type: string }
        content: { type: string }
        createdAt: { type: string, format: date-time }

    InboxItem:
      type: object
      properties:
        id: { type: string }
        ownerActorId: { type: string }
        kind: { type: string }
        state: { $ref: "#/components/schemas/InboxState" }
        title: { type: string }
        threadId: { type: string }

    Ticket:
      type: object
      properties:
        id: { type: string }
        subject: { type: string }
        state: { $ref: "#/components/schemas/TicketState" }
        priority: { $ref: "#/components/schemas/TicketPriority" }
        threadId: { type: string }
        assignedActorId: { type: string }

    OutboundCommand:
      type: object
      properties:
        id: { type: string }
        channelType: { type: string }
        target: { type: string }
        status: { type: string, enum: [PENDING, SENT, FAILED, RATE_LIMITED, BLOCKED] }
        providerRef: { type: string }
        consentReceiptRef: { type: string }
        idempotencyKey: { type: string }

    CreateBinding:
      type: object
      required: [originChannelType, destinationChannelType]
      properties:
        originChannelType: { type: string }
        originRef: { type: string }
        destinationChannelType: { type: string }
        destinationRef: { type: string }
        restrictionProfile: { type: string, example: CHILD }
        mode: { $ref: "#/components/schemas/RouteMode" }
        outputFormat: { $ref: "#/components/schemas/OutputFormat" }
        frequency: { type: string }
        includeAttachments: { type: boolean }
        requiresReview: { type: boolean }
        enabled: { type: boolean }

    RouteBinding:
      allOf:
        - $ref: "#/components/schemas/CreateBinding"
        - type: object
          properties:
            id: { type: string }

    RouteDecision:
      type: object
      properties:
        id: { type: string }
        interactionId: { type: string }
        routeBindingId: { type: string }
        decisionState: { $ref: "#/components/schemas/RouteDecisionState" }
        reasonCodes: { type: array, items: { type: string } }
        previewPayload: { type: object }
        outputFormat: { $ref: "#/components/schemas/OutputFormat" }
        executed: { type: boolean }

    AiArtifact:
      type: object
      properties:
        id: { type: string }
        jobId: { type: string }
        artifactType: { type: string, enum: [CLASSIFICATION, SUMMARY, TRANSCRIPT, DIGEST, EXTRACTION] }
        status: { $ref: "#/components/schemas/AiArtifactStatus" }
        confidence: { type: number, nullable: true }
        contentRef: { type: object }
        abstentionReason: { type: string, nullable: true }
        claims: { type: array, items: { type: object } }
        evidence: { type: array, items: { type: object } }
        modelRun: { type: object }

    ScheduleMeeting:
      type: object
      required: [meetingType, title, startAt]
      properties:
        meetingType: { $ref: "#/components/schemas/MeetingType" }
        title: { type: string }
        startAt: { type: string, format: date-time }
        endAt: { type: string, format: date-time }
        timezone: { type: string }
        requestId: { type: string }
        attendees:
          type: array
          items:
            type: object
            required: [userId]
            properties:
              userId: { type: string }
              displayName: { type: string }
              role: { type: string, enum: [REQUIRED, OPTIONAL] }
              visibility: { type: string, enum: [FULL, LIMITED, NONE] }

    Meeting:
      type: object
      properties:
        id: { type: string }
        meetingType: { $ref: "#/components/schemas/MeetingType" }
        status: { type: string, enum: [REQUESTED, SCHEDULED, CONFIRMED, CANCELLED, COMPLETED, NO_SHOW] }
        title: { type: string }
        organizerActorId: { type: string }
        startAt: { type: string, format: date-time, nullable: true }
        timezone: { type: string }
        participants: { type: array, items: { type: object } }
