openapi: 3.0.0
info:
  version: "1"
  title: MeetOne Video API
  description: >
    ## Documentation usage

    In order to render this documentation you can paste it's whole contents into the swagger editor at https://editor.swagger.io/

    ## Introduction

    This describes the MeetOne Video API which provides endpoints for managing video rooms.

    ## Authentication/authorization information:

    - The API is protected against unauthorized access.

    - A valid token must be provided in the `Authorization` header of each request.

    - **Example**: `Authorization: Bearer eyJhbGciOiJIUzI1****`

    ### API Token Types


    There are two types of API tokens with different scopes:


    #### Instance API Token (Full Access)


    Instance tokens are created by administrators and provide full access to all API resources.


    **Capabilities:**

    - Create, read, update, and delete rooms


    **How to obtain:**

    1. Login as an admin user into the MeetOne application

    2. Navigate to the API Tokens section via the side menu, or directly at `https://xxx.meetone.io/admin_ng/api_tokens`

    3. Create an API token, giving it a name and an expiry date

    4. Copy the token from the details page


    #### Organization API Token (Scoped Access)


    Organization tokens are designed for organizations to integrate with their own systems.


    **Capabilities:**

    - Create rooms — the room is owned by the token's organization

    - Read, update, close and delete the organization's own rooms

    - Read signaling data for the organization's own rooms

    - List rooms belonging to the organization or to its doctors


    Rooms and appointments of other organizations are not visible: they answer
    `404`, exactly as an unknown token would.


    **Rate Limiting:**

    - 20 requests per minute (sustained average)

    - Burst allowance of up to 50 requests per minute


    **How to obtain:**

    1. Login as an organization owner

    2. Navigate to the Integrations page

    3. Your API token is displayed in the API section

    4. Use the "Regenerate" button if you need a new token


    An administrator of the organization can also create additional tokens
    under Developer → API Tokens in the admin area.

    ## OpenAPI client

    You can use tools like [Open API generator](https://github.com/OpenAPITools/openapi-generator) or [Swagger Codegen](https://swagger.io/tools/swagger-codegen/) to generate an API client in the programming language of your choice.

servers:
  - url: https://{tenant}.meetone.io
    description: Production server (tenant-specific)
    variables:
      tenant:
        default: api
        description: Your tenant subdomain

tags:
  - name: Rooms
    description: Room management
  - name: Feedbacks
    description: Room feedback management (Instance API Token required)

paths:
  /api/room_feedbacks:
    get:
      tags:
        - Feedbacks
      summary: Fetch a paginated list of room feedbacks
      operationId: listRoomFeedbacks
      description: >
        Returns a paginated list of room feedbacks across all tenants.

        **Note:** This endpoint requires an Instance API Token.

        ### Pagination

        Use the `page` query parameter to paginate results (e.g. `?page=2`). Defaults to page 1 if omitted.

        The following response headers are returned:

        - `Current-Page`: The current returned page

        - `Page-Limit`: The maximum number of items per page (default: 20)

        - `Total-Pages`: The total number of pages

        - `Total-Count`: The total number of items

        - `Link`: Navigation links in RFC 5988 format with `first`, `last`, `next`, and `prev` relations

      parameters:
        - in: query
          name: page
          schema:
            type: integer
            default: 1
          description: "Page number for pagination"
        - in: query
          name: rating
          schema:
            type: string
            enum:
              - good
              - neutral
              - bad
          description: "Filter feedbacks by rating"
        - in: query
          name: from
          schema:
            type: string
            format: date
          description: "Filter feedbacks created on or after this date (YYYY-MM-DD)"
          example: "2024-01-01"
        - in: query
          name: to
          schema:
            type: string
            format: date
          description: "Filter feedbacks created on or before this date (YYYY-MM-DD, inclusive end of day)"
          example: "2024-12-31"
      responses:
        "200":
          description: Paginated room feedbacks
          headers:
            Current-Page:
              schema:
                type: string
              description: The current page number
            Page-Limit:
              schema:
                type: string
              description: The maximum number of items per page
            Total-Pages:
              schema:
                type: string
              description: The total number of pages
            Total-Count:
              schema:
                type: string
              description: The total number of items
            Link:
              schema:
                type: string
              description: Navigation links (RFC 5988) with rel="first", "last", "next", "prev"
          content:
            application/json:
              schema:
                type: object
                properties:
                  room_feedbacks:
                    type: array
                    items:
                      $ref: "#/components/schemas/RoomFeedback"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden - Requires Instance API Token

  /api/room_feedbacks/summary:
    get:
      tags:
        - Feedbacks
      summary: Get aggregated feedback summary
      operationId: getRoomFeedbacksSummary
      description: >
        Returns aggregated counts of room feedbacks grouped by rating.

        **Note:** This endpoint requires an Instance API Token.
      parameters:
        - in: query
          name: from
          schema:
            type: string
            format: date
          description: "Filter feedbacks created on or after this date (YYYY-MM-DD)"
          example: "2024-01-01"
        - in: query
          name: to
          schema:
            type: string
            format: date
          description: "Filter feedbacks created on or before this date (YYYY-MM-DD, inclusive end of day)"
          example: "2024-12-31"
      responses:
        "200":
          description: Aggregated feedback summary
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedbackSummary"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden - Requires Instance API Token

  /api/rooms:
    get:
      tags:
        - Rooms
      summary: Fetch a list of rooms
      operationId: listRooms
      description: >
        Use to fetch a list of rooms

        ### Pagination

        Use the `page` query parameter to paginate results (e.g. `?page=2`). Defaults to page 1 if omitted.

        The following response headers are returned:

        - `Current-Page`: The current returned page

        - `Page-Limit`: The maximum number of items per page (default: 20)

        - `Total-Pages`: The total number of pages

        - `Total-Count`: The total number of items

        - `Link`: Navigation links in RFC 5988 format with `first`, `last`, `next`, and `prev` relations

        Requesting a page beyond the last page returns a `200` response with an empty `rooms` array.

      parameters:
        - in: query
          name: page
          schema:
            type: integer
            default: 1
          description: "Page number for pagination"
      responses:
        "200":
          description: Paginated rooms
          headers:
            Current-Page:
              schema:
                type: string
              description: The current page number
            Page-Limit:
              schema:
                type: string
              description: The maximum number of items per page
            Total-Pages:
              schema:
                type: string
              description: The total number of pages
            Total-Count:
              schema:
                type: string
              description: The total number of items
            Link:
              schema:
                type: string
              description: Navigation links (RFC 5988) with rel="first", "last", "next", "prev"
          content:
            application/json:
              schema:
                type: object
                properties:
                  rooms:
                    type: array
                    items:
                      $ref: "#/components/schemas/Room"

        "401":
          description: Unauthorized

    post:
      tags:
        - Rooms
      summary: Create a room
      operationId: createRoom
      description: >
        Use to create a room.

        **Note:** An Organization API Token creates the room inside its own organization; an Instance API Token creates a room that belongs to no organization.

        ### Room creation

        A room is created by passing the parameters `name`, `scheduled_at` and `meeting_duration`.

        The name will be displayed to the participants on the join call page.

        The `scheduled_at` is being used to display the distance to the meeting start time. There's no technical implication of this scheduled_at timestamp. Rooms are usable from the moment they exist until they get closed or deleted.

        The `return_url` can be passed on creation or uses a tenant based default. It is being used to send the participants to this url when leaving the room.

        The `host_return_url` can optionally be passed to redirect the host to a different URL when leaving the room. If not provided, the host will be redirected to the same URL as guests (`return_url`).

        The `host_display_name` and `guest_display_name` can optionally be set to pre-fill the participant's display name in the video call. If not provided, participants can enter their name on the join page.

        ## Using rooms

        A room is usable right after creating it until it gets closed.

        The host_join_url is for the host of the room and the guest_join_url is for the guest.

        The join urls can be have a GET parameter `name` to pre-fill the name of the participant, e.g `?name=John+Smith`.

        ## Closing a room

        A room can be closed by sending a request to the `/close` endpoint.

        There's the possibility to configure a cronjob to close rooms automatically. The job runs every 12 hours and closes rooms which have `scheduled_at` more than 24 hours in the past.

        ## Additional guests

        When the `appointment_additional_participants` feature is enabled, you can create additional anonymous access tokens for extra participants by passing `additional_guest_count`.

        Each token allows one additional participant to join the room independently. The tokens are returned in the `additional_access_tokens` array in the response, each with its own `token` and `join_url`.

        These tokens are anonymous (not linked to a specific participant) and can be distributed to anyone who needs to join the room.

        ## Permanent rooms

        The `permanent` flag is gated behind the `permanent_rooms` feature toggle and is **not available by default**. It must be enabled per tenant or per organization on a plan that includes it.

        When the toggle is disabled, submitting `permanent: true` returns HTTP `422 Unprocessable Content` with the error message `"Permanent rooms are not available in your current plan."`. Submitting `permanent: false` (or omitting the field) continues to work normally.

      requestBody:
        description: "Room configuration including name, duration, and scheduling"
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoomRequestBody'
      responses:
        "201":
          description: The created room
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Room"
        "400":
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "401":
          description: Unauthorized
        "422":
          description: >
            Unprocessable Content - Returned when `permanent: true` is submitted but the
            `permanent_rooms` feature toggle is not enabled for the tenant/organization.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "Permanent rooms are not available in your current plan."

  /api/rooms/{token}:
    get:
      tags:
        - Rooms
      summary: Fetch a specific room by `token`
      operationId: getRoom
      description: Use to retrieve a specific room
      parameters:
        - in: path
          name: token
          schema:
            type: string
          required: true
          description: "`token` of the room to fetch"
      responses:
        "200":
          description: The room matching the provided token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Room"
        "401":
          description: Unauthorized
        "404":
          description: No room was found for the `token`

    put:
      tags:
        - Rooms
      summary: Update a room
      operationId: updateRoom
      description: Use to update a room
      parameters:
        - in: path
          name: token
          schema:
            type: string
          required: true
          description: "`token` of the room to update"
      requestBody:
        description: "Updated room configuration"
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoomRequestBody'
      responses:
        "200":
          description: The updated room
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Room"
        "401":
          description: Unauthorized
        "404":
          description: No room was found for the `token`

    delete:
      tags:
        - Rooms
      summary: Delete a room
      operationId: deleteRoom
      description: Use to delete a room
      parameters:
        - in: path
          name: token
          schema:
            type: string
          required: true
          description: "`token` of the room to delete"
      responses:
        "204":
          description: Success
        "401":
          description: Unauthorized

  /api/rooms/{token}/close:
    put:
      tags:
        - Rooms
      summary: Close a room
      operationId: closeRoom
      description: Use to close a room. A closed room can not be accessed anymore.
      parameters:
        - in: path
          name: token
          schema:
            type: string
          required: true
          description: "`token` of the room to close"
      responses:
        "204":
          description: Success
        "401":
          description: Unauthorized

  /api/rooms/{room_token}/signaling:
    get:
      tags:
        - Rooms
      summary: Get WebRTC signaling configuration
      operationId: getRoomSignaling
      description: >
        Use to retrieve WebRTC signaling configuration for establishing video calls.

        Returns the WebSocket URL, ICE servers, and other signaling information needed to establish a peer-to-peer connection.

        **Note:** This endpoint is only relevant when implementing a custom WebRTC client. It is not needed for standard room creation and management via the other endpoints.
      parameters:
        - in: path
          name: room_token
          schema:
            type: string
          required: true
          description: "Access token or guest token of the room"
      responses:
        "200":
          description: Signaling configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RoomSignaling"
        "401":
          description: Unauthorized
        "404":
          description: Room not found

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "Bearer token authentication using API tokens created in the admin panel"
  schemas:
    Error:
      type: array
      description: "Array of validation errors returned when a request fails"
      items:
        type: object
        properties:
          attribute:
            type: string
            example: name
          type:
            type: string
            example: blank
          message:
            type: string
            example: can't be blank

    RoomRequestBody:
      type: object
      description: "Request body for creating or updating a video room"
      required:
        - name
        - meeting_duration
        - scheduled_at
      properties:
        name:
          type: string
          example: "My new room"
        meeting_duration:
          type: number
          description: "Expected duration in minutes"
          example: 45
        scheduled_at:
          type: string
          example: "2024-08-23T15:19:36.378+02:00"
          format: date-time
        return_url:
          type: string
          format: uri
          description: "URL to redirect guests to when leaving the room. Uses tenant default if not provided."
          example: "https://example.com/thank-you"
        host_return_url:
          type: string
          format: uri
          description: "URL to redirect the host to when leaving the room. If not provided, the host will be redirected to the same URL as guests (return_url)."
          example: "https://example.com/host-dashboard"
        host_display_name:
          type: string
          description: "Display name shown to the host participant in the video call. If not provided, the host can enter their name on the join page."
          example: "Dr. Smith"
        guest_display_name:
          type: string
          description: "Display name shown to the guest participant in the video call. If not provided, the guest can enter their name on the join page."
          example: "John Doe"
        enable_chat:
          type: boolean
          description: "Enable or disable chat functionality in the room. Defaults to true."
          example: true
        enable_screenshare:
          type: boolean
          description: "Enable or disable screen sharing in the room. Defaults to true."
          example: true
        enable_invites:
          type: boolean
          description: "Enable or disable the ability to invite additional participants to the room. Defaults to true."
          example: true
        enable_audio_indicator:
          type: boolean
          description: "Enable or disable the audio level indicator (waveform) shown inside the call. The audio indicator on the join screen is always shown regardless of this setting. Defaults to true."
          example: true
        additional_guest_count:
          type: number
          description: "Number of additional anonymous guest access tokens to create. Each token allows one additional participant to join the room. Only available when appointment_additional_participants feature is enabled."
          example: 2
        permanent:
          type: boolean
          description: >
            Mark the room as permanent. Permanent rooms are excluded from the automatic 24h
            cleanup job, do not auto-complete via 'complete on leave', and hide the scheduled-at
            timestamp on the lobby screen. Defaults to false.

            **Gated**: this attribute is only accepted when the `permanent_rooms` feature toggle
            is enabled for the tenant/organization, which is **not the case by default**.
            Submitting `permanent: true` without the toggle enabled returns HTTP 422.
          example: false

    Room:
      type: object
      description: "A video room for hosting video calls between participants"
      properties:
        name:
          type: string
          example: "My room"
        token:
          type: string
          description: "The token of the room. Used to access the room."
          example: f167d6f76f14991c
        status:
          type: string
          example: active
          enum:
            - active
            - started
            - ended
            - closed
        access_token:
          type: string
          description: "Token to access a room as a host."
          example: 65a56167f28f6dec
        guest_token:
          type: string
          description: "Token to access a room as a guest."
          example: "2367668999959557"
        started_at:
          type: string
          format: date-time
          example: "2024-08-23T15:19:36.378+02:00"
        ended_at:
          type: string
          example: "2024-08-23T15:19:36.378+02:00"
          format: date-time
        created_at:
          type: string
          example: "2024-08-23T15:19:36.378+02:00"
          format: date-time
        updated_at:
          type: string
          example: "2024-08-23T15:19:36.378+02:00"
          format: date-time
        return_url:
          type: string
          format: uri
          description: "URL to redirect guests to when leaving the room."
          example: "http://example.com/rooms/login"
        host_return_url:
          type: string
          format: uri
          description: "URL to redirect the host to when leaving the room. If not set, falls back to return_url."
          example: "http://example.com/host-dashboard"
        host_display_name:
          type: string
          nullable: true
          description: "Display name shown to the host participant in the video call."
          example: "Dr. Smith"
        guest_display_name:
          type: string
          nullable: true
          description: "Display name shown to the guest participant in the video call."
          example: "John Doe"
        enable_chat:
          type: boolean
          example: true
        enable_screenshare:
          type: boolean
          example: true
        enable_invites:
          type: boolean
          example: true
        enable_audio_indicator:
          type: boolean
          example: true
        permanent:
          type: boolean
          description: >
            Whether the room is permanent. Permanent rooms are excluded from the automatic 24h
            cleanup, do not auto-complete via 'complete on leave', and hide the scheduled-at
            timestamp on the lobby screen.

            Creating or updating a room with `permanent: true` requires the `permanent_rooms`
            feature toggle to be enabled for the tenant/organization, which is **not the case by
            default**.
          example: false
        scheduled_at:
          type: string
          example: "2024-08-23T15:19:36.378+02:00"
          format: date-time
        closed_at:
          type: string
          example: "2024-08-23T15:19:36.378+02:00"
          format: date-time
        meeting_duration:
          type: number
          example: 15
        current_participants:
          type: array
          items:
            type: string
            example: "2367668999959557"
        expected_guest_count:
          type: number
          example: 2
        host_join_url:
          type: string
          format: uri
          description: "The URL to join the room as a host."
          example: "http://www.example.com/r/7bbb22b73d4e4732"
        guest_join_url:
          type: string
          format: uri
          description: "The URL to join the room as a guest."
          example: "http://www.example.com/r/3d4e4722b7327bbb"
        additional_access_tokens:
          type: array
          description: "Additional room access tokens for extra participants. Only included when appointment_additional_participants feature is enabled."
          items:
            type: object
            properties:
              id:
                type: integer
                example: 42
              token:
                type: string
                example: "abc123def456"
              display_name:
                type: string
                nullable: true
                description: "Display name shown to this participant in the video call."
                example: "Jane Doe"
              join_url:
                type: string
                format: uri
                example: "http://www.example.com/r/abc123def456"
              participant:
                type: object
                nullable: true
                description: "Participant information if token is linked to a specific participant, null for anonymous tokens"
                properties:
                  id:
                    type: integer
                    example: 123
                  name:
                    type: string
                    example: "John Doe"
                  email:
                    type: string
                    format: email
                    example: "john@example.com"
                  phone_number:
                    type: string
                    example: "+1234567890"

    RoomSignaling:
      type: object
      description: "WebRTC signaling configuration for establishing video calls"
      properties:
        room_token:
          type: string
          description: "The room token used for the request"
          example: "65a56167f28f6dec"
        signaling:
          type: object
          properties:
            websocket_url:
              type: string
              description: "WebSocket URL for real-time communication"
              example: "wss://example.meetone.io/cable"
            room_id:
              type: string
              description: "Room identifier for the signaling channel"
              example: "f167d6f76f14991c"
            channel:
              type: string
              description: "ActionCable channel name"
              example: "CallChannel"
            protocol:
              type: string
              description: "WebSocket protocol"
              example: "actioncable"
        ice_servers:
          type: array
          description: "TURN/STUN server configuration for WebRTC"
          items:
            type: object
            properties:
              urls:
                type: array
                items:
                  type: string
                  example: "turn:turn.example.com:443?transport=udp"
              username:
                type: string
                example: "1234567890:username"
              credential:
                type: string
                example: "credential-token"
        status:
          type: string
          description: "Current room status"
          example: "active"
        name:
          type: string
          description: "Room name"
          example: "Dr. Smith Consultation"
        scheduled_at:
          type: string
          format: date-time
          example: "2024-08-23T15:19:36.378+02:00"

    RoomFeedback:
      type: object
      description: "Feedback submitted for a video room"
      properties:
        room_token:
          type: string
          description: "Token of the room this feedback belongs to"
          example: "abc123xyz"
        rating:
          type: string
          description: "Rating given by the participant"
          enum:
            - good
            - neutral
            - bad
          example: "good"
        feedback:
          type: string
          description: "Optional text feedback from the participant"
          example: "Great experience with the video call"
        created_at:
          type: string
          format: date-time
          description: "When the feedback was submitted"
          example: "2024-08-23T15:19:36.378+02:00"

    FeedbackSummary:
      type: object
      description: "Aggregated feedback counts"
      properties:
        total:
          type: integer
          description: "Total number of feedbacks"
          example: 42
        good:
          type: integer
          description: "Number of good ratings"
          example: 30
        neutral:
          type: integer
          description: "Number of neutral ratings"
          example: 8
        bad:
          type: integer
          description: "Number of bad ratings"
          example: 4
        from:
          type: string
          format: date
          nullable: true
          description: "Start date filter applied (if any)"
          example: "2024-01-01"
        to:
          type: string
          format: date
          nullable: true
          description: "End date filter applied (if any)"
          example: "2024-12-31"
