openapi: 3.1.0
info:
  title: Sierra Realtime API
  description: |
    Public REST API for the Sierra Realtime platform — meetings, webinars, chat,
    recordings, scheduling, billing, and SaaS integrations.

    All authenticated endpoints accept a JWT bearer token issued by
    `POST /auth/login` or `POST /auth/refresh`. Tokens are scoped to the
    authenticated user's organization; cross-tenant access is rejected.

    Rate limits: every API route is limited to **200 requests/minute per IP**.
    A few flows (auth, MFA validation) have additional, stricter quotas.

    Webhooks emitted by Sierra Realtime are signed with HMAC-SHA256 over the
    raw request body using the secret returned when the webhook is created.
    The signature is sent in the `X-Sierra-Signature` header.
  version: 1.0.0
  contact:
    name: Sierra Realtime support
    email: support@epicessentials.xyz
    url: https://realtime.epicessentials.xyz/help
  license:
    name: Commercial — Epic Essentials
    url: https://realtime.epicessentials.xyz/legal/terms
servers:
  - url: https://realtime.epicessentials.xyz/api/v1
    description: Production
  - url: http://localhost:8080/api/v1
    description: Local development

tags:
  - name: Auth
    description: Sign-up, login, refresh, password reset, MFA, SSO.
  - name: Users
    description: Current user profile, avatars, onboarding state.
  - name: Organizations
    description: Org settings, members, invites, branding.
  - name: Billing
    description: Plans, subscriptions, invoices, SSLCommerz IPN.
  - name: Rooms
    description: Real-time meeting rooms — create, join, public guest access.
  - name: Webinars
    description: Webinar lifecycle, registration, polls, Q&A.
  - name: Chat
    description: Channels, messages, threads, reactions.
  - name: Recordings
    description: Cloud recording listing, share links, transcripts.
  - name: Scheduling
    description: Booking pages, scheduling polls.
  - name: Webhooks
    description: Outgoing webhook subscription management.
  - name: System
    description: Health, status, and operational endpoints.

paths:
  /health:
    get:
      tags: [System]
      summary: Liveness + dependency check
      description: |
        Returns aggregate platform health and the status of each dependency
        (Postgres, Redis, MinIO). Used by load balancers and the public
        status page.
      security: []
      responses:
        '200':
          description: Service is reachable. Each dependency reports its own status.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'

  /auth/register:
    post:
      tags: [Auth]
      summary: Register a new account and create an organization
      description: |
        Creates the user, the organization, and a 14-day trial license in a
        single transaction. Returns a JWT pair on success.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
      responses:
        '201':
          description: Account created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSuccess'
        '400': { $ref: '#/components/responses/BadRequest' }
        '409':
          description: Email already in use.

  /auth/login:
    post:
      tags: [Auth]
      summary: Exchange credentials for a JWT pair
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Authenticated.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSuccess'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '423':
          description: MFA required — call `/auth/mfa/validate`.

  /auth/refresh:
    post:
      tags: [Auth]
      summary: Rotate a refresh token
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token: { type: string }
      responses:
        '200':
          description: New token pair.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthSuccess'
        '401': { $ref: '#/components/responses/Unauthorized' }

  /auth/logout:
    post:
      tags: [Auth]
      summary: Invalidate the current refresh token
      responses:
        '204':
          description: Logged out.

  /auth/forgot-password:
    post:
      tags: [Auth]
      summary: Request a password-reset email
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        '204':
          description: Reset email queued (response is identical even if no account exists).

  /users/me:
    get:
      tags: [Users]
      summary: Get the authenticated user
      responses:
        '200':
          description: Current user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401': { $ref: '#/components/responses/Unauthorized' }
    patch:
      tags: [Users]
      summary: Update profile fields (name, time zone, avatar URL)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                timezone: { type: string }
                avatar_url: { type: string, format: uri }
      responses:
        '200':
          description: Updated user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'

  /users/me/onboarded:
    post:
      tags: [Users]
      summary: Mark the onboarding tour complete
      responses:
        '204':
          description: Stored.

  /organizations/me:
    get:
      tags: [Organizations]
      summary: Get the caller's organization
      responses:
        '200':
          description: Organization details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Organization'

  /billing/plans:
    get:
      tags: [Billing]
      summary: List available subscription plans
      security: []
      responses:
        '200':
          description: Plan list.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Plan'

  /billing/subscription:
    get:
      tags: [Billing]
      summary: Get the caller org's current license + trial status
      responses:
        '200':
          description: License snapshot.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/License'

  /billing/subscribe:
    post:
      tags: [Billing]
      summary: Initiate an SSLCommerz checkout for a plan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [plan_id, cycle]
              properties:
                plan_id: { type: string }
                cycle:
                  type: string
                  enum: [monthly, yearly]
      responses:
        '200':
          description: Checkout URL.
          content:
            application/json:
              schema:
                type: object
                properties:
                  gateway_url: { type: string, format: uri }
                  transaction_id: { type: string }

  /billing/cancel:
    post:
      tags: [Billing]
      summary: Cancel auto-renewal at end of current period
      responses:
        '200':
          description: License flagged canceled; access continues until period_end.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/License'

  /billing/invoices:
    get:
      tags: [Billing]
      summary: List paid invoices for the caller org
      responses:
        '200':
          description: Invoice list.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Invoice'

  /billing/ipn:
    post:
      tags: [Billing]
      summary: SSLCommerz IPN callback (gateway-only)
      description: |
        Server-to-server postback from SSLCommerz. Signature validated via
        the `Validate` API; treat any other caller as untrusted. Not used by
        client integrations.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: Acknowledged.

  /rooms:
    post:
      tags: [Rooms]
      summary: Create a meeting room
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RoomCreate'
      responses:
        '201':
          description: Room created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Room'
        '402':
          description: Trial expired or plan does not include this feature.
    get:
      tags: [Rooms]
      summary: List rooms in the caller org
      parameters:
        - in: query
          name: status
          schema: { type: string, enum: [scheduled, live, ended] }
      responses:
        '200':
          description: Room list.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Room'

  /rooms/{id}:
    get:
      tags: [Rooms]
      summary: Get a room by ID
      parameters:
        - $ref: '#/components/parameters/RoomID'
      responses:
        '200':
          description: Room.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Room'
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Rooms]
      summary: Delete a room
      parameters:
        - $ref: '#/components/parameters/RoomID'
      responses:
        '204':
          description: Deleted.

  /rooms/{id}/token:
    post:
      tags: [Rooms]
      summary: Mint a LiveKit access token for the authenticated user
      parameters:
        - $ref: '#/components/parameters/RoomID'
      responses:
        '200':
          description: LiveKit JWT.
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
                  livekit_url: { type: string, format: uri }

  /public/rooms/{id}:
    get:
      tags: [Rooms]
      summary: Get a room's public preview metadata (no auth)
      security: []
      parameters:
        - $ref: '#/components/parameters/RoomID'
      responses:
        '200':
          description: Public preview.
        '404': { $ref: '#/components/responses/NotFound' }

  /public/rooms/{id}/guest-token:
    post:
      tags: [Rooms]
      summary: Mint a guest LiveKit token (no auth, requires room is public)
      security: []
      parameters:
        - $ref: '#/components/parameters/RoomID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 80 }
                captcha_token: { type: string, description: "Cloudflare Turnstile token." }
      responses:
        '200':
          description: Guest LiveKit JWT.
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
                  livekit_url: { type: string, format: uri }

  /webinars:
    get:
      tags: [Webinars]
      summary: List webinars in the caller org
      responses:
        '200':
          description: Webinar list.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Webinar'
    post:
      tags: [Webinars]
      summary: Schedule a new webinar
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebinarCreate'
      responses:
        '201':
          description: Webinar scheduled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Webinar'

  /webinars/{id}/register:
    post:
      tags: [Webinars]
      summary: Register an attendee (auth or anonymous)
      parameters:
        - $ref: '#/components/parameters/WebinarID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, name]
              properties:
                email: { type: string, format: email }
                name: { type: string }
                captcha_token: { type: string }
      responses:
        '201':
          description: Registered. Confirmation email queued.

  /channels:
    get:
      tags: [Chat]
      summary: List channels visible to the caller
      responses:
        '200':
          description: Channel list.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Channel'
    post:
      tags: [Chat]
      summary: Create a channel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 80 }
                is_private: { type: boolean }
                description: { type: string, maxLength: 500 }
      responses:
        '201':
          description: Channel created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Channel'

  /channels/{id}/messages:
    get:
      tags: [Chat]
      summary: List messages in a channel
      parameters:
        - $ref: '#/components/parameters/ChannelID'
        - in: query
          name: cursor
          schema: { type: string }
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
      responses:
        '200':
          description: Messages (newest first).
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: '#/components/schemas/Message' }
                  next_cursor: { type: string, nullable: true }
    post:
      tags: [Chat]
      summary: Send a message
      parameters:
        - $ref: '#/components/parameters/ChannelID'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [content]
              properties:
                content: { type: string, maxLength: 8000 }
                thread_id: { type: string, format: uuid }
      responses:
        '201':
          description: Message stored and broadcast.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Message'

  /recordings:
    get:
      tags: [Recordings]
      summary: List recordings in the caller org
      parameters:
        - in: query
          name: room_id
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: Recording list.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Recording'

  /recordings/{id}:
    get:
      tags: [Recordings]
      summary: Get recording metadata + signed playback URL
      parameters:
        - $ref: '#/components/parameters/RecordingID'
      responses:
        '200':
          description: Recording.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Recording'
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Recordings]
      summary: Permanently delete a recording
      parameters:
        - $ref: '#/components/parameters/RecordingID'
      responses:
        '204':
          description: Deleted.

  /recordings/shared/{token}:
    get:
      tags: [Recordings]
      summary: Public share-link viewer (no auth)
      security: []
      parameters:
        - in: path
          name: token
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Public recording metadata + signed playback URL.

  /booking-pages:
    get:
      tags: [Scheduling]
      summary: List the caller's booking pages
      responses:
        '200':
          description: Booking page list.
    post:
      tags: [Scheduling]
      summary: Create a booking page
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        '201':
          description: Created.

  /public/book/{slug}:
    get:
      tags: [Scheduling]
      summary: Public booking page (no auth)
      security: []
      parameters:
        - in: path
          name: slug
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Booking page.
    post:
      tags: [Scheduling]
      summary: Create a booking
      security: []
      parameters:
        - in: path
          name: slug
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [start_at, name, email]
              properties:
                start_at: { type: string, format: date-time }
                name: { type: string }
                email: { type: string, format: email }
                notes: { type: string }
                captcha_token: { type: string }
      responses:
        '201':
          description: Booking confirmed; confirmation email sent.

  /webhooks:
    get:
      tags: [Webhooks]
      summary: List outgoing webhook subscriptions for the caller org
      responses:
        '200':
          description: Webhook list.
    post:
      tags: [Webhooks]
      summary: Register a new webhook
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url: { type: string, format: uri }
                events:
                  type: array
                  items: { type: string }
      responses:
        '201':
          description: Webhook created. Secret returned ONCE.

  /webhooks/{id}:
    delete:
      tags: [Webhooks]
      summary: Delete a webhook
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        '204':
          description: Deleted.

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Pass `Authorization: Bearer <jwt>`. JWTs are issued by `/auth/login`
        and `/auth/refresh`, signed with the platform JWT secret, and contain
        `sub` (user UUID), `org_id`, and `roles` claims.

  parameters:
    RoomID:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }
    WebinarID:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }
    ChannelID:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }
    RecordingID:
      in: path
      name: id
      required: true
      schema: { type: string, format: uuid }

  responses:
    BadRequest:
      description: Validation error.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: Missing or invalid bearer token.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Resource not found or not visible to this caller.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
        code: { type: string }
      required: [error]

    Health:
      type: object
      properties:
        status: { type: string, enum: [ok, degraded, unhealthy] }
        service: { type: string }
        version: { type: string }
        checks:
          type: array
          items:
            type: object
            properties:
              name: { type: string }
              status: { type: string }

    RegisterRequest:
      type: object
      required: [email, password, name, organization_name]
      properties:
        email: { type: string, format: email }
        password: { type: string, format: password, minLength: 8 }
        name: { type: string }
        organization_name: { type: string }
        captcha_token: { type: string }

    LoginRequest:
      type: object
      required: [email, password]
      properties:
        email: { type: string, format: email }
        password: { type: string, format: password }
        captcha_token: { type: string }

    AuthSuccess:
      type: object
      properties:
        access_token: { type: string }
        refresh_token: { type: string }
        user: { $ref: '#/components/schemas/User' }

    User:
      type: object
      properties:
        id: { type: string, format: uuid }
        email: { type: string, format: email }
        name: { type: string }
        avatar_url: { type: string, format: uri, nullable: true }
        org_id: { type: string, format: uuid }
        roles:
          type: array
          items: { type: string }
        onboarded_at: { type: string, format: date-time, nullable: true }
        timezone: { type: string }
        created_at: { type: string, format: date-time }

    Organization:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        slug: { type: string }
        created_at: { type: string, format: date-time }

    Plan:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        price_monthly_cents: { type: integer }
        price_yearly_cents: { type: integer }
        currency: { type: string, example: BDT }
        max_seats: { type: integer }
        has_webinars: { type: boolean }
        has_ai: { type: boolean }
        has_dlp: { type: boolean }
        has_sso: { type: boolean }

    License:
      type: object
      properties:
        org_id: { type: string, format: uuid }
        plan_id: { type: string }
        status:
          type: string
          enum: [trial, active, canceled, expired]
        trial_start: { type: string, format: date-time, nullable: true }
        trial_end: { type: string, format: date-time, nullable: true }
        current_period_start: { type: string, format: date-time, nullable: true }
        current_period_end: { type: string, format: date-time, nullable: true }
        billing_cycle:
          type: string
          enum: [monthly, yearly]
          nullable: true
        max_seats: { type: integer }
        used_seats: { type: integer }
        has_webinars: { type: boolean }
        has_ai: { type: boolean }
        has_dlp: { type: boolean }
        has_sso: { type: boolean }

    Invoice:
      type: object
      properties:
        id: { type: string, format: uuid }
        amount_cents: { type: integer }
        currency: { type: string }
        status:
          type: string
          enum: [paid, pending, failed, refunded]
        period_start: { type: string, format: date-time }
        period_end: { type: string, format: date-time }
        created_at: { type: string, format: date-time }
        pdf_url: { type: string, format: uri, nullable: true }

    RoomCreate:
      type: object
      required: [name]
      properties:
        name: { type: string, maxLength: 120 }
        is_public: { type: boolean }
        scheduled_for: { type: string, format: date-time }
        password: { type: string }

    Room:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        org_id: { type: string, format: uuid }
        host_id: { type: string, format: uuid }
        is_public: { type: boolean }
        status:
          type: string
          enum: [scheduled, live, ended]
        scheduled_for: { type: string, format: date-time, nullable: true }
        started_at: { type: string, format: date-time, nullable: true }
        ended_at: { type: string, format: date-time, nullable: true }
        livekit_room_name: { type: string }

    WebinarCreate:
      type: object
      required: [title, scheduled_for]
      properties:
        title: { type: string }
        description: { type: string }
        scheduled_for: { type: string, format: date-time }
        max_attendees: { type: integer }

    Webinar:
      type: object
      properties:
        id: { type: string, format: uuid }
        title: { type: string }
        description: { type: string }
        org_id: { type: string, format: uuid }
        host_id: { type: string, format: uuid }
        scheduled_for: { type: string, format: date-time }
        registered_count: { type: integer }
        status:
          type: string
          enum: [scheduled, live, ended]

    Channel:
      type: object
      properties:
        id: { type: string, format: uuid }
        name: { type: string }
        org_id: { type: string, format: uuid }
        is_private: { type: boolean }
        description: { type: string, nullable: true }
        created_at: { type: string, format: date-time }

    Message:
      type: object
      properties:
        id: { type: string, format: uuid }
        channel_id: { type: string, format: uuid }
        sender_id: { type: string, format: uuid }
        content: { type: string }
        thread_id: { type: string, format: uuid, nullable: true }
        created_at: { type: string, format: date-time }
        edited_at: { type: string, format: date-time, nullable: true }

    Recording:
      type: object
      properties:
        id: { type: string, format: uuid }
        room_id: { type: string, format: uuid }
        org_id: { type: string, format: uuid }
        duration_seconds: { type: integer }
        size_bytes: { type: integer }
        status:
          type: string
          enum: [recording, processing, ready, failed]
        share_token: { type: string, nullable: true }
        playback_url: { type: string, format: uri, nullable: true }
        completed_at: { type: string, format: date-time, nullable: true }

security:
  - BearerAuth: []
