openapi: 3.1.0
info:
  title: Sessionboard Public API
  version: '1.0'
  description: REST API for managing sessions, speakers, contacts, sponsors, exhibitors, and event data on the Sessionboard platform.
  contact:
    name: Sessionboard Support
    email: support@sessionboard.com
  termsOfService: https://www.sessionboard.com/legal/terms-of-service
servers:
  - url: https://public-api.sessionboard.com
    description: US Region
  - url: https://public-api-eu.sessionboard.com
    description: EU Region
security:
  - ApiKey: []
  - BearerToken: []
tags:
  - name: Events
    description: Manage events on the platform.
  - name: Sessions
    description: Search and retrieve sessions within an event.
  - name: Session Writes
    description: Create, update, delete, and restore sessions. Requires the `write:sessions` scope.
  - name: Contact Writes
    description: Create, update, delete, and restore contacts. Requires the `write:contacts` scope.
  - name: Exhibitor Writes
    description: Create, update, delete, and restore exhibitors. Requires the `write:exhibitors` scope.
  - name: Sponsor Writes
    description: Create, update, delete, and restore sponsors. Requires the `write:sponsors` scope.
  - name: Field Writes
    description: Create, update, and delete custom fields. Requires the `write:fields` scope.
  - name: Speakers
    description: Search and retrieve speakers within an event.
  - name: Contacts
    description: Search and retrieve contacts at the organization or event level.
  - name: Sponsors
    description: Search and retrieve sponsors within an event.
  - name: Exhibitors
    description: Search and retrieve exhibitors within an event.
  - name: Event Settings
    description: Retrieve event configuration such as fields, tags, tracks, rooms, formats, levels, languages, and session statuses.
  - name: GDPR
    description: Manage GDPR data access and erasure requests.
  - name: Insights
    description: Query event data using SbQL, generate queries with AI, and manage dashboards.
  - name: Metadata Writes
    description: Create, update, and delete session metadata (rooms, tracks, tags, formats, levels, languages, statuses). Requires the `write:metadata` scope.
  - name: Agenda Planning
    description: Manage agenda drafts, draft sessions, scheduling rules, and evaluation personas. Requires the `write:events` scope.
  - name: Dashboards & Widgets
    description: Create, read, update, and delete dashboards and widgets at the event or organization level. Requires `write:events` scope for writes.
  - name: Reports & Queries
    description: Create, read, update, delete, and run saved SbQL queries/reports at the event or organization level. Requires `write:events` scope for writes.
  - name: Transcriptions
    description: Transcript text on sessions — fragments, summaries, insights, and translations. Requires `read:transcriptions` / `write:transcriptions` scopes.
  - name: Session Recordings
    description: Session audio files for recording archives (live capture or imported audio). Requires `read:transcriptions` / `write:transcriptions` scopes.
  - name: Media
    description: Upload video or audio files for automatic transcription. Requires `read:media` / `write:media` scopes.
  - name: Session Files
    description: |
      Documents attached to sessions (PDFs, PowerPoint, Word, images, and similar).
      Simple upload: `POST .../files/upload` (multipart, max 50 MB).
      Direct-to-storage: create → PUT → complete (max 500 MB).
      Requires `read:sessions` / `write:sessions` scopes.
      See the [Uploading session files](/guides/uploading-session-files) guide.
  - name: OAuth
    description: OAuth 2.1 endpoints for AI client authorization (Claude, ChatGPT). Mounted at `/oauth/` (not `/v1/`).

paths:
  # ──────────────────────────────────────────────
  # Events
  # ──────────────────────────────────────────────
  /v1/events:
    get:
      operationId: list-events
      summary: List events
      description: Returns a paginated list of events accessible to the authenticated API key.
      tags:
        - Events
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Event'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized

  # ──────────────────────────────────────────────
  # Sessions
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/sessions:
    post:
      operationId: search-sessions
      summary: Search sessions
      description: |
        Search and filter sessions within an event. Supports pagination,
        sorting, filtering by status and date ranges, and expanding related
        data.

        Every session includes `is_abstract` and `composition_status`. Pass
        `filters.isAbstract` to return only abstract submissions or only
        program sessions. By default, sessions linked as composition sources
        are excluded; pass `expand=linked_sources` to include them. Pass
        `expand=composition` for full target/source details.

        Each session in the response includes a `subsessions` array. By
        default each subsession is returned in a minimal shape (`Subsession`).
        Pass `expand=subsession_details` in the request body to receive full
        parent-shape parity (`SubsessionDetailed`) on every subsession —
        including `status`, `custom_status`, `custom_fields`, `chairpersons`,
        `moderators`, `sponsors`, `exhibitors`, `tags`, `language`, `track`,
        `level`, `room`, etc.

        Each subsession inherits the parent session's `is_abstract`,
        `composition_status`, and optional `composition` graph (when
        `expand=composition` is set).

        Nested `language`, `track`, `level`, `format`, and `room` objects on
        each session include the field sets documented on the `Session` schema.
        Unassigned metadata is returned as `{}`.

        Paginated search responses return matching sessions under `results`
        (not `data`), alongside a `pagination` object.
      tags:
        - Sessions
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Session'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    get:
      operationId: list-sessions
      summary: List sessions (CRUD proxy)
      description: |
        Paginated list of top-level sessions via the CRUD proxy. Returns nested
        `language`, `track`, `level`, `format`, and `room` metadata using the same
        field sets documented on the `Session` schema (`null` when unassigned).
        Also includes `is_abstract` and `composition_status`.

        Query params: `is_abstract` (`true`/`false`), `expand=linked_sources`,
        `expand=composition`, plus standard pagination and filters.
      tags:
        - Sessions
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - name: page_size
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: is_abstract
          in: query
          schema:
            type: boolean
          description: Filter to abstract submissions or program sessions.
        - $ref: '#/components/parameters/Expand'
        - name: status
          in: query
          schema:
            type: string
        - name: track_id
          in: query
          schema:
            type: string
            format: uuid
        - name: tag_id
          in: query
          schema:
            type: string
            format: uuid
        - name: search
          in: query
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Session'
                  pagination:
                    type: object
                    properties:
                      current_page:
                        type: integer
                      page_size:
                        type: integer
                      total_pages:
                        type: integer
                      total_results:
                        type: integer

  /v1/event/{eventId}/sessions/status:
    post:
      operationId: search-sessions-by-status
      summary: Search sessions by status
      description: |
        Returns a lightweight list of session statuses, including deleted
        sessions. Supports filtering by `deletedAt` in addition to standard
        filters.

        Parent rows additionally include a `subsessions` array with the same
        minimal status-focused shape for each child subsession
        (`SubsessionStatus`). Subsession rows that appear in the flat
        results continue to surface with their own row (for back-compat) and
        carry an empty `subsessions` array.
      tags:
        - Sessions
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                filters:
                  type: object
                  properties:
                    createdAt:
                      $ref: '#/components/schemas/DateRangeFilter'
                    updatedAt:
                      $ref: '#/components/schemas/DateRangeFilter'
                    deletedAt:
                      $ref: '#/components/schemas/DateRangeFilter'
                    status:
                      type: string
                      enum:
                        - accepted
                        - accept_queue
                        - pending
                        - decline_queue
                        - declined
                sort:
                  type: object
                  properties:
                    order:
                      type: string
                      enum:
                        - createdAt
                        - updatedAt
                        - deletedAt
                    sort:
                      type: string
                      enum:
                        - asc
                        - desc
                expand:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/SessionStatus'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/sessions/{sessionId}:
    get:
      operationId: get-session
      summary: Get a session
      description: |
        Retrieve a single session by ID, optionally expanding related data.

        Every session includes `is_abstract` and `composition_status`. Pass
        `expand=composition` for full linked target and source details.

        Accepts both parent session UUIDs and subsession UUIDs. When called
        with a subsession UUID, the response is a full session-shaped DTO
        for that subsession (with `parent_session_friendly_id` /
        `parent_session_friendly_id_raw` keys and no nested `subsessions`
        array). Subsessions inherit the parent session's `is_abstract`,
        `composition_status`, and optional `composition` graph (when
        `expand=composition` is set) — subsessions cannot participate in
        composition directly.

        This means callers can iterate `parent.subsessions[]` and then call
        this endpoint with each subsession id to retrieve the same shape
        as a parent session.

        Pass `expand=subsession_details` to receive the full
        `SubsessionDetailed` shape on every entry of `parent.subsessions[]`;
        the flag has no effect at the top level — the top-level response
        always returns the full shape regardless of whether the requested
        id is a parent or a subsession.
      tags:
        - Sessions
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The session ID.
        - $ref: '#/components/parameters/Expand'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '401':
          description: Unauthorized
        '404':
          description: Session not found
    put:
      operationId: update-session
      summary: Update a session
      description: Update an existing session. Requires the `write:sessions` scope. Send `updated_at` from the last-fetched session for optimistic concurrency control.
      tags:
        - Session Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The session ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                updated_at:
                  type: string
                  format: date-time
                  description: Required for optimistic concurrency control. Send the `updated_at` value from the last-fetched session.
                title:
                  type: string
                description:
                  type: string
                starts_at:
                  type: string
                  format: date-time
                ends_at:
                  type: string
                  format: date-time
                capacity:
                  type: integer
                room_id:
                  type: string
                  format: uuid
                track_id:
                  type: string
                  format: uuid
                status:
                  type: string
                tag_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                is_public:
                  type: boolean
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Session updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sessions scope
        '404':
          description: Session not found
        '409':
          description: Stale update — session modified since last fetch
    delete:
      operationId: delete-session
      summary: Soft-delete a session
      description: Soft-delete a session. Can be restored with the restore endpoint. Requires the `write:sessions` scope.
      tags:
        - Session Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The session ID.
      responses:
        '200':
          description: Session deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sessions scope
        '404':
          description: Session not found
        '409':
          description: Cannot delete — has active subsessions

  /v1/event/{eventId}/sessions/{sessionId}/fields:
    put:
      operationId: update-session-fields
      summary: Update session custom fields
      description: Update custom field values on a session without touching other session properties. Requires the `write:sessions` scope.
      tags:
        - Session Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The session ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Custom fields updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sessions scope
        '404':
          description: Session not found

  # ──────────────────────────────────────────────
  # Speakers
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/speakers:
    post:
      operationId: search-speakers
      summary: Search speakers
      description: Search and filter speakers within an event.
      tags:
        - Speakers
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/speakers/{contactId}:
    get:
      operationId: get-speaker
      summary: Get a speaker
      description: Retrieve a single speaker by contact ID.
      tags:
        - Speakers
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: The contact ID.
        - $ref: '#/components/parameters/Expand'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          description: Unauthorized
        '404':
          description: Speaker not found

  # ──────────────────────────────────────────────
  # Contacts (Organization-level)
  # ──────────────────────────────────────────────
  /v1/organization/{orgId}/contacts:
    post:
      operationId: search-org-contacts
      summary: Search organization contacts
      description: Search and filter contacts across the organization.
      tags:
        - Contacts
      parameters:
        - name: orgId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The organization ID (UUID).
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Organization not found

  /v1/organization/{orgId}/contacts/{contactId}:
    get:
      operationId: get-org-contact
      summary: Get an organization contact
      description: Retrieve a single contact by ID at the organization level.
      tags:
        - Contacts
      parameters:
        - name: orgId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The organization ID (UUID).
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: The contact ID.
        - $ref: '#/components/parameters/Expand'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          description: Unauthorized
        '404':
          description: Contact not found

  # ──────────────────────────────────────────────
  # Contacts (Event-level)
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/contacts:
    post:
      operationId: search-event-contacts
      summary: Search event contacts
      description: Search and filter contacts within an event.
      tags:
        - Contacts
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Contact'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/contacts/{contactId}:
    get:
      operationId: get-event-contact
      summary: Get an event contact
      description: Retrieve a single contact by ID at the event level.
      tags:
        - Contacts
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: The contact ID.
        - $ref: '#/components/parameters/Expand'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          description: Unauthorized
        '404':
          description: Contact not found
    put:
      operationId: update-contact
      summary: Update a contact
      description: Update an existing contact. Requires the `write:contacts` scope. Optionally send `updated_at` for optimistic concurrency control.
      tags:
        - Contact Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: The contact ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                updated_at:
                  type: string
                  format: date-time
                  description: Optional optimistic concurrency control. Send the `updated_at` value from the last-fetched contact.
                first_name:
                  type: string
                last_name:
                  type: string
                email:
                  type: string
                  format: email
                company:
                  type: string
                title:
                  type: string
                phone:
                  type: string
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Contact updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:contacts scope
        '404':
          description: Contact not found
        '409':
          description: Stale update — contact modified since last fetch
    delete:
      operationId: delete-contact
      summary: Soft-delete a contact
      description: Soft-delete a contact. Can be restored with the restore endpoint. Requires the `write:contacts` scope.
      tags:
        - Contact Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: The contact ID.
      responses:
        '200':
          description: Contact deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:contacts scope
        '404':
          description: Contact not found

  # ──────────────────────────────────────────────
  # Sponsors
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/sponsors:
    post:
      operationId: search-sponsors
      summary: Search sponsors
      description: Search and filter sponsors within an event.
      tags:
        - Sponsors
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Sponsor'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/sponsors/{sponsorId}:
    get:
      operationId: get-sponsor
      summary: Get a sponsor
      description: Retrieve a single sponsor by ID.
      tags:
        - Sponsors
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sponsorId
          in: path
          required: true
          schema:
            type: string
          description: The sponsor ID.
        - $ref: '#/components/parameters/Expand'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sponsor'
        '401':
          description: Unauthorized
        '404':
          description: Sponsor not found
    put:
      operationId: update-sponsor
      summary: Update a sponsor
      description: Update an existing sponsor. Requires the `write:sponsors` scope. Optionally send `updated_at` for optimistic concurrency control.
      tags:
        - Sponsor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sponsorId
          in: path
          required: true
          schema:
            type: string
          description: The sponsor ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                updated_at:
                  type: string
                  format: date-time
                  description: Optional optimistic concurrency control. Send the `updated_at` value from the last-fetched sponsor.
                name:
                  type: string
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Sponsor updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sponsor'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sponsors scope
        '404':
          description: Sponsor not found
        '409':
          description: Stale update — sponsor modified since last fetch
    delete:
      operationId: delete-sponsor
      summary: Soft-delete a sponsor
      description: Soft-delete a sponsor. Can be restored with the restore endpoint. Requires the `write:sponsors` scope.
      tags:
        - Sponsor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sponsorId
          in: path
          required: true
          schema:
            type: string
          description: The sponsor ID.
      responses:
        '200':
          description: Sponsor deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sponsors scope
        '404':
          description: Sponsor not found

  # ──────────────────────────────────────────────
  # Exhibitors
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/exhibitors:
    post:
      operationId: search-exhibitors
      summary: Search exhibitors
      description: Search and filter exhibitors within an event.
      tags:
        - Exhibitors
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Exhibitor'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/exhibitors/{exhibitorId}:
    get:
      operationId: get-exhibitor
      summary: Get an exhibitor
      description: Retrieve a single exhibitor by ID.
      tags:
        - Exhibitors
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: exhibitorId
          in: path
          required: true
          schema:
            type: string
          description: The exhibitor ID.
        - $ref: '#/components/parameters/Expand'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Exhibitor'
        '401':
          description: Unauthorized
        '404':
          description: Exhibitor not found
    put:
      operationId: update-exhibitor
      summary: Update an exhibitor
      description: Update an existing exhibitor. Requires the `write:exhibitors` scope. Optionally send `updated_at` for optimistic concurrency control.
      tags:
        - Exhibitor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: exhibitorId
          in: path
          required: true
          schema:
            type: string
          description: The exhibitor ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                updated_at:
                  type: string
                  format: date-time
                  description: Optional optimistic concurrency control. Send the `updated_at` value from the last-fetched exhibitor.
                name:
                  type: string
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Exhibitor updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Exhibitor'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:exhibitors scope
        '404':
          description: Exhibitor not found
        '409':
          description: Stale update — exhibitor modified since last fetch
    delete:
      operationId: delete-exhibitor
      summary: Soft-delete an exhibitor
      description: Soft-delete an exhibitor. Can be restored with the restore endpoint. Requires the `write:exhibitors` scope.
      tags:
        - Exhibitor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: exhibitorId
          in: path
          required: true
          schema:
            type: string
          description: The exhibitor ID.
      responses:
        '200':
          description: Exhibitor deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:exhibitors scope
        '404':
          description: Exhibitor not found

  # ──────────────────────────────────────────────
  # Event Settings
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/fields:
    get:
      operationId: list-fields
      summary: List fields
      description: Returns all field definitions for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Field'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    post:
      operationId: search-fields
      summary: Search fields
      description: Search and filter field definitions for an event, including standard and custom fields.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                filters:
                  type: object
                  properties:
                    module:
                      type: string
                      enum:
                        - session
                        - account
                        - contact
                      description: Filter by module type.
                    source:
                      type: string
                      enum:
                        - standard
                        - custom
                      description: Filter by field source.
                    scope:
                      type: string
                      enum:
                        - global
                        - event
                      description: Filter by field scope.
                    createdAt:
                      $ref: '#/components/schemas/DateRangeFilter'
                    updatedAt:
                      $ref: '#/components/schemas/DateRangeFilter'
                sort:
                  $ref: '#/components/schemas/SortOptions'
                expand:
                  type: array
                  items:
                    type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Field'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/tags:
    get:
      operationId: list-tags
      summary: List tags
      description: Returns all tags for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tag'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    post:
      operationId: search-tags
      summary: Search tags
      description: Returns a paginated list of tags for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Tag'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/languages:
    get:
      operationId: list-languages
      summary: List languages
      description: Returns all languages for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Language'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    post:
      operationId: search-languages
      summary: Search languages
      description: Returns a paginated list of languages configured for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Language'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/formats:
    get:
      operationId: list-formats
      summary: List formats
      description: Returns all session formats for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Format'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    post:
      operationId: search-formats
      summary: Search formats
      description: Returns a paginated list of session formats for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Format'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/tracks:
    get:
      operationId: list-tracks
      summary: List tracks
      description: Returns all tracks for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Track'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    post:
      operationId: search-tracks
      summary: Search tracks
      description: Returns a paginated list of tracks for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Track'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/levels:
    get:
      operationId: list-levels
      summary: List levels
      description: Returns all session levels for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Level'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    post:
      operationId: search-levels
      summary: Search levels
      description: Returns a paginated list of session levels for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Level'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/rooms:
    get:
      operationId: list-rooms
      summary: List rooms
      description: Returns all rooms for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Room'
        '401':
          description: Unauthorized
        '404':
          description: Event not found
    post:
      operationId: search-rooms
      summary: Search rooms
      description: Returns a paginated list of rooms for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/Room'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/statuses:
    get:
      operationId: list-statuses
      summary: List session statuses
      description: Returns all session status definitions for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SessionCustomStatus'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  /v1/event/{eventId}/session-statuses:
    post:
      operationId: search-session-statuses
      summary: Search session statuses
      description: Returns a paginated list of custom session status definitions for an event.
      tags:
        - Event Settings
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSearchBody'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/SessionCustomStatus'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Event not found

  # ──────────────────────────────────────────────
  # Metadata Write Operations
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/rooms/create:
    post:
      operationId: create-room
      summary: Create a room
      description: Create a new room. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                capacity: { type: integer }
      responses:
        '200':
          description: Room created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Room'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/rooms/{id}:
    put:
      operationId: update-room
      summary: Update a room
      description: Update a room. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                capacity: { type: integer }
      responses:
        '200':
          description: Room updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Room'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }
    delete:
      operationId: delete-room
      summary: Delete a room
      description: Delete a room. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Room deleted }
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/tracks/create:
    post:
      operationId: create-track
      summary: Create a track
      description: Create a new track. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                color: { type: string }
      responses:
        '200':
          description: Track created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Track'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/tracks/{id}:
    put:
      operationId: update-track
      summary: Update a track
      description: Update a track. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                color: { type: string }
      responses:
        '200':
          description: Track updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Track'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }
    delete:
      operationId: delete-track
      summary: Delete a track
      description: Delete a track. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Track deleted }
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/tags/create:
    post:
      operationId: create-tag
      summary: Create a tag
      description: Create a new tag. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
      responses:
        '200':
          description: Tag created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/tags/{id}:
    put:
      operationId: update-tag
      summary: Update a tag
      description: Update a tag. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
      responses:
        '200':
          description: Tag updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tag'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }
    delete:
      operationId: delete-tag
      summary: Delete a tag
      description: Delete a tag. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Tag deleted }
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/formats/create:
    post:
      operationId: create-format
      summary: Create a format
      description: Create a new session format. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
      responses:
        '200':
          description: Format created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Format'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/formats/{id}:
    put:
      operationId: update-format
      summary: Update a format
      description: Update a session format. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
      responses:
        '200':
          description: Format updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Format'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }
    delete:
      operationId: delete-format
      summary: Delete a format
      description: Delete a session format. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Format deleted }
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/levels/create:
    post:
      operationId: create-level
      summary: Create a level
      description: Create a new session level. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
      responses:
        '200':
          description: Level created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Level'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/levels/{id}:
    put:
      operationId: update-level
      summary: Update a level
      description: Update a session level. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
      responses:
        '200':
          description: Level updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Level'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }
    delete:
      operationId: delete-level
      summary: Delete a level
      description: Delete a session level. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Level deleted }
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/languages/create:
    post:
      operationId: create-language
      summary: Create a language
      description: Create a new language. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
      responses:
        '200':
          description: Language created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Language'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/languages/{id}:
    put:
      operationId: update-language
      summary: Update a language
      description: Update a language. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
      responses:
        '200':
          description: Language updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Language'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }
    delete:
      operationId: delete-language
      summary: Delete a language
      description: Delete a language. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Language deleted }
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/statuses/create:
    post:
      operationId: create-status
      summary: Create a session status
      description: Create a new custom session status. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                color: { type: string }
      responses:
        '200':
          description: Status created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionCustomStatus'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/statuses/{id}:
    put:
      operationId: update-status
      summary: Update a session status
      description: Update a custom session status. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                color: { type: string }
      responses:
        '200':
          description: Status updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionCustomStatus'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }
    delete:
      operationId: delete-status
      summary: Delete a session status
      description: Delete a custom session status. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Status deleted }
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  /v1/event/{eventId}/statuses/{id}/restore:
    post:
      operationId: restore-status
      summary: Restore a deleted session status
      description: Restore a previously soft-deleted session status. Requires the `write:metadata` scope.
      tags: [Metadata Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Status restored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionCustomStatus'
        '401': { description: Unauthorized }
        '403': { description: Missing write:metadata scope }

  # ──────────────────────────────────────────────
  # Agenda Drafts
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/agenda-drafts:
    get:
      operationId: list-agenda-drafts
      summary: List agenda drafts
      description: Returns all agenda drafts for an event.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AgendaDraft'
        '401':
          description: Unauthorized

  /v1/event/{eventId}/agenda-drafts/create:
    post:
      operationId: create-agenda-draft
      summary: Create an agenda draft
      description: Create a new agenda draft workspace. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  description: Draft name.
                description:
                  type: string
                  description: Optional description.
      responses:
        '200':
          description: Draft created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgendaDraft'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/agenda-drafts/{draftId}:
    get:
      operationId: get-agenda-draft
      summary: Get an agenda draft
      description: Retrieve a single agenda draft by ID.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The draft ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgendaDraft'
        '401':
          description: Unauthorized
        '404':
          description: Draft not found
    put:
      operationId: update-agenda-draft
      summary: Update an agenda draft
      description: Update an agenda draft. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
      responses:
        '200':
          description: Draft updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgendaDraft'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-agenda-draft
      summary: Delete an agenda draft
      description: Delete an agenda draft. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Draft deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/agenda-drafts/{draftId}/changes:
    get:
      operationId: get-agenda-draft-changes
      summary: Preview draft changes
      description: Preview the pending changes in a draft before committing them to the live schedule.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                description: A summary of additions, modifications, and removals.
        '401':
          description: Unauthorized

  /v1/event/{eventId}/agenda-drafts/{draftId}/commit:
    post:
      operationId: commit-agenda-draft
      summary: Commit an agenda draft
      description: Apply all changes in the draft to the live event schedule. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Draft committed
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Draft Sessions (within Agenda Drafts)
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/agenda-drafts/{draftId}/sessions:
    get:
      operationId: list-draft-sessions
      summary: List draft sessions
      description: List session placements within an agenda draft.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DraftSession'
        '401':
          description: Unauthorized

  /v1/event/{eventId}/agenda-drafts/{draftId}/sessions/create:
    post:
      operationId: create-draft-session
      summary: Create a draft session
      description: Place a session in an agenda draft. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - session_id
              properties:
                session_id:
                  type: string
                  format: uuid
                starts_at:
                  type: string
                  format: date-time
                ends_at:
                  type: string
                  format: date-time
                room_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Draft session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftSession'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/agenda-drafts/{draftId}/sessions/{draftSessionId}:
    put:
      operationId: update-draft-session
      summary: Update a draft session
      description: Move or update a session placement in a draft. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: draftSessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                starts_at:
                  type: string
                  format: date-time
                ends_at:
                  type: string
                  format: date-time
                room_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Draft session updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DraftSession'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-draft-session
      summary: Remove a draft session
      description: Remove a session from an agenda draft. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: draftSessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Draft session removed
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/agenda-drafts/{draftId}/sessions/bulk:
    post:
      operationId: bulk-draft-sessions
      summary: Bulk draft session operations
      description: Batch create, update, or delete session placements in a draft. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: draftId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkOperations'
      responses:
        '200':
          description: Bulk operation completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkResponse'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Event Rules
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/rules:
    get:
      operationId: list-event-rules
      summary: List event rules
      description: Returns all scheduling rules for an event.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EventRule'
        '401':
          description: Unauthorized

  /v1/event/{eventId}/rules/create:
    post:
      operationId: create-event-rule
      summary: Create an event rule
      description: Create a scheduling constraint. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - type
              properties:
                name:
                  type: string
                type:
                  type: string
                config:
                  type: object
                  additionalProperties: true
                enabled:
                  type: boolean
                  default: true
      responses:
        '200':
          description: Rule created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventRule'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/rules/{ruleId}:
    get:
      operationId: get-event-rule
      summary: Get an event rule
      description: Retrieve a single event rule by ID.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: ruleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventRule'
        '401':
          description: Unauthorized
        '404':
          description: Rule not found
    put:
      operationId: update-event-rule
      summary: Update an event rule
      description: Update a scheduling rule. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: ruleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                config:
                  type: object
                  additionalProperties: true
                enabled:
                  type: boolean
      responses:
        '200':
          description: Rule updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventRule'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-event-rule
      summary: Delete an event rule
      description: Delete a scheduling rule. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: ruleId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Rule deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Personas
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/personas:
    get:
      operationId: list-personas
      summary: List personas
      description: Returns all evaluation personas for an event.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Persona'
        '401':
          description: Unauthorized

  /v1/event/{eventId}/personas/create:
    post:
      operationId: create-persona
      summary: Create a persona
      description: Create an attendee persona for schedule evaluation. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                description:
                  type: string
                config:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: Persona created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Persona'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/personas/{personaId}:
    get:
      operationId: get-persona
      summary: Get a persona
      description: Retrieve a single persona by ID.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: personaId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Persona'
        '401':
          description: Unauthorized
        '404':
          description: Persona not found
    put:
      operationId: update-persona
      summary: Update a persona
      description: Update a persona. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: personaId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                config:
                  type: object
                  additionalProperties: true
      responses:
        '200':
          description: Persona updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Persona'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-persona
      summary: Delete a persona
      description: Delete a persona. Requires the `write:events` scope.
      tags:
        - Agenda Planning
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: personaId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Persona deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Dashboard CRUD
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/dashboards:
    get:
      operationId: list-event-dashboards
      summary: List dashboards
      description: Returns all dashboards for an event. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                    name:
                      type: string
                    created_at:
                      type: string
                      format: date-time
                    updated_at:
                      type: string
                      format: date-time
        '401':
          description: Unauthorized

  /v1/event/{eventId}/dashboards/create:
    post:
      operationId: create-event-dashboard
      summary: Create a dashboard
      description: Create a new dashboard with optional inline widgets. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                description:
                  type: string
                widgets:
                  type: array
                  items:
                    type: object
                  description: Optional inline widget definitions to create with the dashboard.
      responses:
        '200':
          description: Dashboard created
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/dashboards/{id}:
    get:
      operationId: get-event-dashboard
      summary: Get a dashboard
      description: Retrieve a dashboard with all widget data. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  widgets:
                    type: array
                    items:
                      type: object
        '401':
          description: Unauthorized
        '404':
          description: Dashboard not found
    put:
      operationId: update-event-dashboard
      summary: Update a dashboard
      description: Update dashboard properties. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
      responses:
        '200':
          description: Dashboard updated
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-event-dashboard
      summary: Delete a dashboard
      description: Delete a dashboard. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Dashboard deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Widget CRUD
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/dashboards/{dashboardId}/widgets/create:
    post:
      operationId: create-widget
      summary: Create a widget
      description: Add a widget to a dashboard. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - type
              properties:
                type:
                  type: string
                  description: Widget type (e.g., chart, table, number).
                title:
                  type: string
                config:
                  type: object
                  additionalProperties: true
                  description: Widget-specific configuration.
                query:
                  type: string
                  description: SbQL query for the widget data.
      responses:
        '200':
          description: Widget created
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/widgets/{widgetId}:
    put:
      operationId: update-widget
      summary: Update a widget
      description: Update widget configuration. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: widgetId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                config:
                  type: object
                  additionalProperties: true
                query:
                  type: string
      responses:
        '200':
          description: Widget updated
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-widget
      summary: Delete a widget
      description: Delete a widget from its dashboard. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: widgetId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Widget deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Saved Queries / Reports CRUD
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/queries:
    get:
      operationId: list-event-queries
      summary: List saved queries for an event
      description: Returns saved SbQL queries scoped to an event. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        query:
                          type: string
                        category:
                          type: string
        '401':
          description: Unauthorized

  /v1/event/{eventId}/queries/create:
    post:
      operationId: create-event-query
      summary: Create a saved query
      description: Save a SbQL query as a reusable report. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - query
              properties:
                name:
                  type: string
                query:
                  type: string
                  description: The SbQL query string.
                category:
                  type: string
      responses:
        '200':
          description: Query saved
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/queries/{queryId}:
    put:
      operationId: update-event-query
      summary: Update a saved query
      description: Update a saved query. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: queryId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                query:
                  type: string
                category:
                  type: string
      responses:
        '200':
          description: Query updated
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-event-query
      summary: Delete a saved query
      description: Delete a saved query. Requires the `write:events` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: queryId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Query deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/event/{eventId}/queries/{queryId}/run:
    post:
      operationId: run-event-query
      summary: Run a saved query for an event
      description: Execute a saved SbQL query. Requires the `write:events` scope (reports execute through the write pipeline for quota tracking).
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: queryId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                obfuscatePii:
                  type: boolean
                  default: true
                page:
                  type: integer
                limit:
                  type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Query not found

  # ──────────────────────────────────────────────
  # Convenience Dashboard Routes (org resolved from token)
  # ──────────────────────────────────────────────
  /v1/dashboards:
    get:
      operationId: list-org-dashboards-convenience
      summary: List org dashboards (convenience)
      description: Returns all dashboards across all events in the organization. Organization is resolved from the API token. No orgId required. Requires the `read:insights` scope.
      tags:
        - Dashboards & Widgets
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                    name:
                      type: string
                    event_id:
                      type: integer
                      nullable: true
                    created_at:
                      type: string
                      format: date-time
                    updated_at:
                      type: string
                      format: date-time
        '401':
          description: Unauthorized

  /v1/dashboards/create:
    post:
      operationId: create-org-dashboard-convenience
      summary: Create an org dashboard (convenience)
      description: Create a new cross-event dashboard. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Dashboards & Widgets
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                description:
                  type: string
                widgets:
                  type: array
                  items:
                    type: object
                  description: Optional inline widget definitions.
      responses:
        '200':
          description: Dashboard created
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/dashboards/{id}:
    get:
      operationId: get-org-dashboard-convenience
      summary: Get an org dashboard (convenience)
      description: Retrieve a cross-event dashboard with all widget data. Organization is resolved from the API token. No orgId required. Requires the `read:insights` scope.
      tags:
        - Dashboards & Widgets
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  widgets:
                    type: array
                    items:
                      type: object
        '401':
          description: Unauthorized
        '404':
          description: Dashboard not found
    put:
      operationId: update-org-dashboard-convenience
      summary: Update an org dashboard (convenience)
      description: Update dashboard properties. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Dashboards & Widgets
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
      responses:
        '200':
          description: Dashboard updated
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-org-dashboard-convenience
      summary: Delete an org dashboard (convenience)
      description: Delete a cross-event dashboard. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Dashboards & Widgets
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Dashboard deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Convenience Widget Routes (org resolved from token)
  # ──────────────────────────────────────────────
  /v1/dashboards/{dashboardId}/widgets/create:
    post:
      operationId: create-org-widget-convenience
      summary: Create an org widget (convenience)
      description: Add a widget to an org-level dashboard. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Dashboards & Widgets
      parameters:
        - name: dashboardId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - type
              properties:
                type:
                  type: string
                  description: Widget type (e.g., chart, table, number).
                title:
                  type: string
                config:
                  type: object
                  additionalProperties: true
                query:
                  type: string
                  description: SbQL query for the widget data.
      responses:
        '200':
          description: Widget created
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/widgets/{widgetId}:
    put:
      operationId: update-org-widget-convenience
      summary: Update an org widget (convenience)
      description: Update widget configuration. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Dashboards & Widgets
      parameters:
        - name: widgetId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                config:
                  type: object
                  additionalProperties: true
                query:
                  type: string
      responses:
        '200':
          description: Widget updated
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-org-widget-convenience
      summary: Delete an org widget (convenience)
      description: Delete a widget. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Dashboards & Widgets
      parameters:
        - name: widgetId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Widget deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  # ──────────────────────────────────────────────
  # Convenience Query/Report Routes (org resolved from token)
  # ──────────────────────────────────────────────
  /v1/queries:
    get:
      operationId: list-org-queries-convenience
      summary: List org saved queries (convenience)
      description: Returns all saved SbQL queries across the organization. Organization is resolved from the API token. No orgId required. Requires the `read:insights` scope.
      tags:
        - Reports & Queries
      parameters:
        - name: category
          in: query
          required: false
          schema:
            type: string
          description: Filter by category.
        - name: page
          in: query
          required: false
          schema:
            type: integer
        - name: limit
          in: query
          required: false
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        query:
                          type: string
                        category:
                          type: string
                        event_id:
                          type: integer
                          nullable: true
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized

  /v1/queries/create:
    post:
      operationId: create-org-query-convenience
      summary: Create an org saved query (convenience)
      description: Save a cross-event SbQL query. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Reports & Queries
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - query
              properties:
                name:
                  type: string
                query:
                  type: string
                  description: The SbQL query string.
                category:
                  type: string
      responses:
        '200':
          description: Query saved
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/queries/{queryId}:
    put:
      operationId: update-org-query-convenience
      summary: Update an org saved query (convenience)
      description: Update a saved query. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Reports & Queries
      parameters:
        - name: queryId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                query:
                  type: string
                category:
                  type: string
      responses:
        '200':
          description: Query updated
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope
    delete:
      operationId: delete-org-query-convenience
      summary: Delete an org saved query (convenience)
      description: Delete a saved query. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope.
      tags:
        - Reports & Queries
      parameters:
        - name: queryId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Query deleted
        '401':
          description: Unauthorized
        '403':
          description: Missing write:events scope

  /v1/queries/{queryId}/run:
    post:
      operationId: run-org-query-convenience
      summary: Run an org saved query (convenience)
      description: Execute a saved cross-event SbQL query. Organization is resolved from the API token. No orgId required. Requires the `write:events` scope (reports execute through the write pipeline for quota tracking).
      tags:
        - Reports & Queries
      parameters:
        - name: queryId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                obfuscatePii:
                  type: boolean
                  default: true
                page:
                  type: integer
                limit:
                  type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '404':
          description: Query not found

  # ──────────────────────────────────────────────
  # Event-Scoped Insights
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/insights/execute:
    post:
      operationId: execute-event-query
      summary: Execute a SbQL query (event-scoped)
      description: Execute a SbQL query scoped to a specific event. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID to scope the query to.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                obfuscatePii:
                  type: boolean
                  default: true
                page:
                  type: integer
                limit:
                  type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '400':
          description: Invalid query
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/event/{eventId}/insights/ai/generate:
    post:
      operationId: generate-event-query
      summary: Generate a SbQL query from natural language (event-scoped)
      description: Use AI to translate natural language into a SbQL query scoped to a specific event. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - prompt
              properties:
                prompt:
                  type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  query:
                    type: string
                  explanation:
                    type: string
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/event/{eventId}/insights/schema:
    get:
      operationId: get-event-insights-schema
      summary: Get event-scoped insights schema
      description: Returns the SbQL schema including event-specific custom fields. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/event/{eventId}/insights/suggestions:
    get:
      operationId: get-event-suggestions
      summary: Get event-scoped query suggestions
      description: Returns contextual query suggestions for a specific event. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  suggestions:
                    type: array
                    items:
                      type: object
                      properties:
                        query:
                          type: string
                        description:
                          type: string
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/event/{eventId}/insights/queries:
    get:
      operationId: list-event-insights-queries
      summary: List saved queries (event-scoped insights)
      description: Returns saved queries via the insights proxy. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/event/{eventId}/insights/queries/{queryId}/run:
    post:
      operationId: run-event-insights-query
      summary: Run a saved query (event-scoped insights)
      description: Execute a saved query via the insights proxy. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: queryId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/event/{eventId}/insights/dashboards/{id}:
    get:
      operationId: get-event-insights-dashboard
      summary: Get a dashboard (event-scoped insights)
      description: Retrieve a dashboard via the insights proxy. Requires the `read:insights` scope.
      tags:
        - Insights
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  widgets:
                    type: array
                    items:
                      type: object
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  # ──────────────────────────────────────────────
  # GDPR
  # ──────────────────────────────────────────────
  /v1/gdpr/requests:
    get:
      operationId: list-gdpr-requests
      summary: List GDPR requests
      description: Returns all GDPR data access and erasure requests for the organization.
      tags:
        - GDPR
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      $ref: '#/components/schemas/GDPRRequest'
        '401':
          description: Unauthorized
    post:
      operationId: create-gdpr-request
      summary: Create a GDPR request
      description: Submit a new GDPR data access or erasure request.
      tags:
        - GDPR
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - requestType
                - identityType
                - identityValue
              properties:
                requestType:
                  type: string
                  enum:
                    - erasure
                    - access
                  description: The type of GDPR request.
                identityType:
                  type: string
                  enum:
                    - email
                  description: The type of identity to look up.
                identityValue:
                  type: string
                  description: The identity value (e.g., an email address).
                  examples:
                    - jane.doe@example.com
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GDPRRequest'
        '400':
          description: Bad request
        '401':
          description: Unauthorized

  # ──────────────────────────────────────────────
  # Insights
  # ──────────────────────────────────────────────
  /v1/insights/execute:
    post:
      operationId: execute-query
      summary: Execute a SbQL query
      description: Execute a SbQL query against event data and return results. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - query
              properties:
                query:
                  type: string
                  description: The SbQL query string to execute.
                  examples:
                    - "SELECT sessions.title, sessions.status FROM sessions"
                eventId:
                  type: integer
                  description: Scope query to a single event.
                eventIds:
                  type: array
                  items:
                    type: integer
                  description: Scope query to multiple events.
                obfuscatePii:
                  type: boolean
                  default: true
                  description: Whether to obfuscate personally identifiable information in results.
                format:
                  type: string
                  description: Output format for results.
                page:
                  type: integer
                  description: Page number for paginated results.
                limit:
                  type: integer
                  description: Maximum number of rows to return.
                search:
                  type: string
                  description: Full-text search within results.
                sortBy:
                  type: string
                  description: Column name to sort results by.
                sortDirection:
                  type: string
                  enum:
                    - asc
                    - desc
                  description: Sort direction.
                groupBy:
                  type: string
                  description: Column name to group results by.
                pivot:
                  type: string
                  description: Column name to pivot results on.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '400':
          description: Invalid query
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/insights/ai/generate:
    post:
      operationId: generate-query
      summary: Generate a SbQL query from natural language
      description: Use AI to translate a natural language prompt into a SbQL query. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - prompt
              properties:
                prompt:
                  type: string
                  description: Natural language description of the data you want to query.
                  examples:
                    - "Show me all accepted sessions with their speakers"
                eventId:
                  type: integer
                  description: Scope to a single event for context.
                eventIds:
                  type: array
                  items:
                    type: integer
                  description: Scope to multiple events for context.
                conversationHistory:
                  type: array
                  items:
                    type: object
                    properties:
                      role:
                        type: string
                        description: The message role (e.g., user, assistant).
                      content:
                        type: string
                        description: The message content.
                  description: Previous conversation turns for multi-turn query refinement.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  query:
                    type: string
                    description: The generated SbQL query.
                  explanation:
                    type: string
                    description: Human-readable explanation of the generated query.
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/insights/schema:
    get:
      operationId: get-schema
      summary: Get insights schema
      description: Returns the SbQL schema for the organization, including available entities and fields. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                description: The SbQL schema definition.
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/insights/events/{eventId}/schema:
    get:
      operationId: get-event-schema
      summary: Get event-specific insights schema
      description: Returns the SbQL schema scoped to a specific event, including event-specific custom fields. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                description: The event-scoped SbQL schema definition.
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope
        '404':
          description: Event not found

  /v1/insights/suggestions:
    get:
      operationId: get-suggestions
      summary: Get query suggestions
      description: Returns contextual SbQL query suggestions. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      parameters:
        - name: eventId
          in: query
          required: false
          schema:
            type: integer
          description: Scope suggestions to a specific event.
        - name: category
          in: query
          required: false
          schema:
            type: string
          description: Filter suggestions by category.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  suggestions:
                    type: array
                    items:
                      type: object
                      properties:
                        query:
                          type: string
                        description:
                          type: string
                        category:
                          type: string
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/insights/queries:
    get:
      operationId: list-queries
      summary: List saved queries
      description: Returns a list of saved SbQL queries. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      parameters:
        - name: eventId
          in: query
          required: false
          schema:
            type: integer
          description: Filter queries by event.
        - name: category
          in: query
          required: false
          schema:
            type: string
          description: Filter queries by category.
        - name: page
          in: query
          required: false
          schema:
            type: integer
          description: Page number.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
          description: Results per page.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        query:
                          type: string
                        category:
                          type: string
                        eventId:
                          type: integer
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope

  /v1/insights/queries/{queryId}/run:
    post:
      operationId: run-query
      summary: Run a saved query
      description: Execute a previously saved SbQL query by ID. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      parameters:
        - name: queryId
          in: path
          required: true
          schema:
            type: string
          description: The saved query ID.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                eventId:
                  type: integer
                  description: Override the event scope for this run.
                eventIds:
                  type: array
                  items:
                    type: integer
                  description: Override with multiple event scopes.
                obfuscatePii:
                  type: boolean
                  default: true
                format:
                  type: string
                page:
                  type: integer
                limit:
                  type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      type: object
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope
        '404':
          description: Saved query not found

  /v1/insights/dashboards/{id}:
    get:
      operationId: get-dashboard
      summary: Get a dashboard
      description: Retrieve a dashboard with all its widget data. Requires the `read:insights` scope.
      tags:
        - Insights
      security:
        - ApiKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: The dashboard ID.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  name:
                    type: string
                  widgets:
                    type: array
                    items:
                      type: object
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope
        '404':
          description: Dashboard not found

  # ──────────────────────────────────────────────
  # Session CRUD (Write Operations)
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/sessions/create:
    post:
      operationId: create-session
      summary: Create a session
      description: Create a new session in an event. Requires the `write:sessions` scope. Subject to rate limiting (100 req/15min) and daily write quota (10,000/day per token).
      tags:
        - Session Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - title
              properties:
                title:
                  type: string
                  description: Session title.
                description:
                  type: string
                  description: Session description (HTML allowed).
                starts_at:
                  type: string
                  format: date-time
                  description: Session start time.
                ends_at:
                  type: string
                  format: date-time
                  description: Session end time.
                capacity:
                  type: integer
                  description: Maximum capacity for the session.
                room_id:
                  type: string
                  format: uuid
                  description: Room ID to assign.
                track_id:
                  type: string
                  format: uuid
                  description: Track ID to assign.
                level_id:
                  type: string
                  format: uuid
                  description: Level ID to assign.
                format_id:
                  type: string
                  format: uuid
                  description: Format ID to assign.
                language_id:
                  type: string
                  format: uuid
                  description: Language ID to assign.
                parent_session_id:
                  type: string
                  format: uuid
                  description: Parent session ID (for subsessions).
                tag_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Tag IDs to assign.
                status:
                  type: string
                  description: Session status (e.g., pending, accepted).
                is_public:
                  type: boolean
                  description: Whether the session is publicly visible.
                is_abstract:
                  type: boolean
                  default: false
                  description: |
                    Create an abstract (CFP) submission instead of a program session.
                    Requires the Sessions 2.0 feature on the event.
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Session created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '400':
          description: Validation error
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope or feature not enabled
        '429':
          description: Rate limit or daily quota exceeded

  /v1/event/{eventId}/sessions/{sessionId}/restore:
    post:
      operationId: restore-session
      summary: Restore a deleted session
      description: Restore a previously soft-deleted session. Requires the `write:sessions` scope.
      tags:
        - Session Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: The session ID.
      responses:
        '200':
          description: Session restored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Session'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sessions scope
        '404':
          description: Session not found or not deleted

  /v1/event/{eventId}/sessions/bulk:
    post:
      operationId: bulk-sessions
      summary: Bulk session operations
      description: Create, update, or delete multiple sessions in a single request (max 100 operations). Requires the `write:sessions` scope.
      tags:
        - Session Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkOperations'
      responses:
        '200':
          description: Bulk operation completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkResponse'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sessions scope
        '429':
          description: Rate limit or daily quota exceeded

  # ──────────────────────────────────────────────
  # Contact Write Operations
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/contacts/create:
    post:
      operationId: create-contact
      summary: Create a contact
      description: Create a new contact in an event. Requires the `write:contacts` scope. Subject to rate limiting (100 req/15min) and daily write quota (10,000/day per token).
      tags:
        - Contact Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                  description: Contact email address (required, must be unique within the event).
                first_name:
                  type: string
                  description: First name.
                last_name:
                  type: string
                  description: Last name.
                company:
                  type: string
                  description: Company or organization name.
                title:
                  type: string
                  description: Job title.
                phone:
                  type: string
                  description: Phone number.
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Contact created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '400':
          description: Validation error
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope or feature not enabled
        '429':
          description: Rate limit or daily quota exceeded


  /v1/event/{eventId}/contacts/{contactId}/restore:
    post:
      operationId: restore-contact
      summary: Restore a deleted contact
      description: Restore a previously soft-deleted contact. Requires the `write:contacts` scope.
      tags:
        - Contact Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: contactId
          in: path
          required: true
          schema:
            type: string
          description: The contact ID.
      responses:
        '200':
          description: Contact restored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:contacts scope
        '404':
          description: Contact not found or not deleted

  /v1/event/{eventId}/contacts/bulk:
    post:
      operationId: bulk-contacts
      summary: Bulk contact operations
      description: Create, update, or delete multiple contacts in a single request (max 100 operations). Requires the `write:contacts` scope.
      tags:
        - Contact Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkOperations'
      responses:
        '200':
          description: Bulk operation completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkResponse'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:contacts scope
        '429':
          description: Rate limit or daily quota exceeded

  # ──────────────────────────────────────────────
  # Exhibitor Write Operations
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/exhibitors/create:
    post:
      operationId: create-exhibitor
      summary: Create an exhibitor
      description: Create a new exhibitor in an event. Requires the `write:exhibitors` scope. Subject to rate limiting (100 req/15min) and daily write quota (10,000/day per token).
      tags:
        - Exhibitor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Exhibitor name. Required if `account_id` is not provided.
                account_id:
                  type: string
                  format: uuid
                  description: Existing account ID to link. Required if `name` is not provided.
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Exhibitor created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Exhibitor'
        '400':
          description: Validation error
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope or feature not enabled
        '429':
          description: Rate limit or daily quota exceeded


  /v1/event/{eventId}/exhibitors/{exhibitorId}/restore:
    post:
      operationId: restore-exhibitor
      summary: Restore a deleted exhibitor
      description: Restore a previously soft-deleted exhibitor. Requires the `write:exhibitors` scope.
      tags:
        - Exhibitor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: exhibitorId
          in: path
          required: true
          schema:
            type: string
          description: The exhibitor ID.
      responses:
        '200':
          description: Exhibitor restored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Exhibitor'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:exhibitors scope
        '404':
          description: Exhibitor not found or not deleted

  /v1/event/{eventId}/exhibitors/bulk:
    post:
      operationId: bulk-exhibitors
      summary: Bulk exhibitor operations
      description: Create, update, or delete multiple exhibitors in a single request (max 100 operations). Requires the `write:exhibitors` scope.
      tags:
        - Exhibitor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkOperations'
      responses:
        '200':
          description: Bulk operation completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkResponse'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:exhibitors scope
        '429':
          description: Rate limit or daily quota exceeded

  # ──────────────────────────────────────────────
  # Sponsor Write Operations
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/sponsors/create:
    post:
      operationId: create-sponsor
      summary: Create a sponsor
      description: Create a new sponsor in an event. Requires the `write:sponsors` scope. Subject to rate limiting (100 req/15min) and daily write quota (10,000/day per token).
      tags:
        - Sponsor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Sponsor name. Required if `account_id` is not provided.
                account_id:
                  type: string
                  format: uuid
                  description: Existing account ID to link. Required if `name` is not provided.
                custom_fields:
                  type: object
                  additionalProperties: true
                  description: Custom field values keyed by field internal name.
      responses:
        '200':
          description: Sponsor created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sponsor'
        '400':
          description: Validation error
        '401':
          description: Unauthorized
        '403':
          description: Insufficient scope or feature not enabled
        '429':
          description: Rate limit or daily quota exceeded


  /v1/event/{eventId}/sponsors/{sponsorId}/restore:
    post:
      operationId: restore-sponsor
      summary: Restore a deleted sponsor
      description: Restore a previously soft-deleted sponsor. Requires the `write:sponsors` scope.
      tags:
        - Sponsor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
        - name: sponsorId
          in: path
          required: true
          schema:
            type: string
          description: The sponsor ID.
      responses:
        '200':
          description: Sponsor restored
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Sponsor'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sponsors scope
        '404':
          description: Sponsor not found or not deleted

  /v1/event/{eventId}/sponsors/bulk:
    post:
      operationId: bulk-sponsors
      summary: Bulk sponsor operations
      description: Create, update, or delete multiple sponsors in a single request (max 100 operations). Requires the `write:sponsors` scope.
      tags:
        - Sponsor Writes
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
          description: The event ID.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BulkOperations'
      responses:
        '200':
          description: Bulk operation completed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BulkResponse'
        '401':
          description: Unauthorized
        '403':
          description: Missing write:sponsors scope
        '429':
          description: Rate limit or daily quota exceeded

  # ──────────────────────────────────────────────
  # Field Write Operations
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/fields/create:
    post:
      operationId: create-field
      summary: Create a custom field
      description: Create a new custom field for an event module. Requires the `write:fields` scope.
      tags: [Field Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                type: { type: string }
                module_id: { type: string }
      responses:
        '200': { description: Field created }
        '401': { description: Unauthorized }
        '403': { description: Missing write:fields scope }

  /v1/event/{eventId}/fields/{fieldId}:
    put:
      operationId: update-field
      summary: Update a custom field
      tags: [Field Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: fieldId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        '200': { description: Field updated }
        '401': { description: Unauthorized }
    delete:
      operationId: delete-field
      summary: Delete a custom field
      tags: [Field Writes]
      parameters:
        - name: eventId
          in: path
          required: true
          schema: { type: integer }
        - name: fieldId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Field deleted }
        '401': { description: Unauthorized }

  # ──────────────────────────────────────────────
  # OAuth 2.1
  # ──────────────────────────────────────────────
  /oauth/authorize:
    get:
      operationId: oauth-authorize
      summary: Validate authorization request
      description: Validates OAuth authorization request parameters (client_id, redirect_uri, scopes, PKCE challenge). Returns the validated request for display on the consent page. No authentication required.
      tags:
        - OAuth
      security: []
      parameters:
        - name: client_id
          in: query
          required: true
          schema:
            type: string
        - name: redirect_uri
          in: query
          required: true
          schema:
            type: string
        - name: response_type
          in: query
          required: true
          schema:
            type: string
            enum: [code]
        - name: code_challenge
          in: query
          required: true
          schema:
            type: string
        - name: code_challenge_method
          in: query
          required: true
          schema:
            type: string
            enum: [S256]
        - name: scope
          in: query
          schema:
            type: string
          description: Space-separated scopes
        - name: state
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Authorization request validated
          content:
            application/json:
              schema:
                type: object
                properties:
                  authorization_request:
                    type: object
                    properties:
                      client_id:
                        type: string
                      client_name:
                        type: string
                      redirect_uri:
                        type: string
                      scopes:
                        type: array
                        items:
                          type: string
        '400':
          description: Invalid request parameters

  /oauth/token:
    post:
      operationId: oauth-token
      summary: Exchange code for tokens
      description: Exchange an authorization code for access and refresh tokens (authorization_code grant), or refresh an expired access token (refresh_token grant). Uses PKCE verification — no client secret required.
      tags:
        - OAuth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - type: object
                  title: Authorization Code Exchange
                  required: [grant_type, code, redirect_uri, client_id, code_verifier]
                  properties:
                    grant_type:
                      type: string
                      enum: [authorization_code]
                    code:
                      type: string
                    redirect_uri:
                      type: string
                    client_id:
                      type: string
                    code_verifier:
                      type: string
                      description: PKCE code verifier
                - type: object
                  title: Refresh Token
                  required: [grant_type, refresh_token, client_id]
                  properties:
                    grant_type:
                      type: string
                      enum: [refresh_token]
                    refresh_token:
                      type: string
                    client_id:
                      type: string
      responses:
        '200':
          description: Tokens issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token:
                    type: string
                  token_type:
                    type: string
                    example: Bearer
                  expires_in:
                    type: integer
                    example: 3600
                  refresh_token:
                    type: string
                  scope:
                    type: string
        '400':
          description: Invalid grant or parameters

  /oauth/revoke:
    post:
      operationId: oauth-revoke
      summary: Revoke a token
      description: Revoke an access or refresh token. Conforms to RFC 7009. Always returns 200 regardless of whether the token was found.
      tags:
        - OAuth
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token:
                  type: string
                token_type_hint:
                  type: string
                  enum: [access_token, refresh_token]
      responses:
        '200':
          description: Token revoked (or was already invalid)

  /oauth/.well-known/oauth-authorization-server:
    get:
      operationId: oauth-metadata
      summary: Authorization server metadata
      description: Returns OAuth 2.0 Authorization Server Metadata per RFC 8414. Includes supported scopes, grant types, and endpoint URLs.
      tags:
        - OAuth
      security: []
      responses:
        '200':
          description: Server metadata
          content:
            application/json:
              schema:
                type: object
                properties:
                  issuer:
                    type: string
                  authorization_endpoint:
                    type: string
                  token_endpoint:
                    type: string
                  revocation_endpoint:
                    type: string
                  response_types_supported:
                    type: array
                    items:
                      type: string
                  grant_types_supported:
                    type: array
                    items:
                      type: string
                  scopes_supported:
                    type: array
                    items:
                      type: string
                  code_challenge_methods_supported:
                    type: array
                    items:
                      type: string

  # ──────────────────────────────────────────────
  # Contact Sessions
  # ──────────────────────────────────────────────
  /v1/event/{eventId}/contacts/{contactId}/sessions:
    get:
      operationId: get-contact-sessions
      summary: Get a contact's sessions
      description: |
        Returns all sessions associated with a specific contact within an
        event, including linked composition sources. Each session summary
        includes `is_abstract` and `composition_status`. The full
        `composition` graph is not available on this lightweight endpoint —
        use session GET or search with `expand=composition` for details.
      tags:
        - Contacts
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: integer
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactSessionsResponse'
        '401':
          description: Unauthorized
        '404':
          description: Contact not found

  /v1/organization/{orgId}/contacts/{contactId}/sessions:
    get:
      operationId: get-org-contact-sessions
      summary: Get an org contact's sessions
      description: |
        Returns all sessions associated with a specific contact across the
        organization, including linked composition sources. Each session
        summary includes `is_abstract`, `composition_status`, and
        `event_id`. The full `composition` graph is not available on this
        lightweight endpoint.
      tags:
        - Contacts
      parameters:
        - name: orgId
          in: path
          required: true
          schema:
            type: string
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OrgContactSessionsResponse'
        '401':
          description: Unauthorized
        '404':
          description: Contact not found

  /v1/event/{eventId}/transcriptions:
    get:
      operationId: list-event-transcriptions
      summary: List transcriptions for an event
      description: Paginated list of transcription artifacts across all sessions. Filter by type, session_id, or language_code. Requires `read:transcriptions` scope (legacy tokens with empty scopes pass).
      tags: [Transcriptions]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - name: type
          in: query
          schema:
            type: string
            enum: [fragment, summary, insight, translation]
        - name: session_id
          in: query
          schema:
            type: string
            format: uuid
        - name: language_code
          in: query
          schema:
            type: string
        - name: include_partial
          in: query
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranscriptionListResponse'
        '403':
          description: Missing read:transcriptions scope

  /v1/event/{eventId}/transcriptions/{transcriptionId}:
    get:
      operationId: get-transcription
      summary: Get a transcription by ID
      description: Requires `read:transcriptions` scope (legacy tokens with empty scopes pass).
      tags: [Transcriptions]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - name: transcriptionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranscriptionResponse'
        '403':
          description: Missing read:transcriptions scope
        '404':
          description: Not found

  /v1/event/{eventId}/sessions/{sessionId}/transcriptions:
    get:
      operationId: list-session-transcriptions
      summary: List transcriptions for a session
      description: Requires `read:transcriptions` scope (legacy tokens with empty scopes pass).
      tags: [Transcriptions]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranscriptionListResponse'
        '403':
          description: Missing read:transcriptions scope
    post:
      operationId: create-transcription
      summary: Create a transcription artifact
      description: Requires `write:transcriptions` scope.
      tags: [Transcriptions]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TranscriptionCreateRequest'
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranscriptionResponse'
        '403':
          description: Missing write:transcriptions scope

  /v1/event/{eventId}/sessions/{sessionId}/transcriptions/{transcriptionId}:
    get:
      operationId: get-session-transcription
      summary: Get a session transcription by ID
      description: Requires `read:transcriptions` scope (legacy tokens with empty scopes pass).
      tags: [Transcriptions]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - name: transcriptionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranscriptionResponse'
        '403':
          description: Missing read:transcriptions scope
        '404':
          description: Not found
    put:
      operationId: update-transcription
      summary: Update a transcription artifact
      description: Requires `write:transcriptions` scope.
      tags: [Transcriptions]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - name: transcriptionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TranscriptionResponse'
        '403':
          description: Missing write:transcriptions scope
    delete:
      operationId: delete-transcription
      summary: Delete a transcription artifact
      description: Requires `write:transcriptions` scope.
      tags: [Transcriptions]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - name: transcriptionId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Deleted
        '403':
          description: Missing write:transcriptions scope

  /v1/event/{eventId}/sessions/{sessionId}/recordings:
    get:
      operationId: list-session-recordings
      summary: List session audio recordings
      description: Requires `read:transcriptions` scope (legacy tokens with empty scopes pass).
      tags: [Session Recordings]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Recording'
        '403':
          description: Missing read:transcriptions scope
    post:
      operationId: create-session-recording
      summary: Initiate session audio upload
      description: Returns a presigned upload URL for the audio file. Requires `write:transcriptions`.
      tags: [Session Recordings]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename, size_bytes]
              properties:
                filename:
                  type: string
                content_type:
                  type: string
                size_bytes:
                  type: integer
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordingCreateResponse'
        '403':
          description: Missing write:transcriptions scope

  /v1/event/{eventId}/sessions/{sessionId}/recordings/{recordingId}:
    get:
      operationId: get-session-recording
      summary: Get a session audio recording
      description: Requires `read:transcriptions` scope (legacy tokens with empty scopes pass).
      tags: [Session Recordings]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - name: recordingId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordingResponse'
        '403':
          description: Missing read:transcriptions scope
        '404':
          description: Not found

  /v1/event/{eventId}/sessions/{sessionId}/recordings/{recordingId}/complete:
    post:
      operationId: complete-session-recording
      summary: Finalize session audio upload
      description: |
        Marks the recording as completed and normalizes common audio formats for playback.
        Already-completed recordings return 200 without re-processing. Requires `write:transcriptions`.
      tags: [Session Recordings]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - name: recordingId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                duration_seconds:
                  type: number
                file_size_bytes:
                  type: integer
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordingResponse'
        '403':
          description: Missing write:transcriptions scope
        '422':
          description: PCM transcode failed (TRANSCODE_ERROR)

  /v1/event/{eventId}/sessions/{sessionId}/media/upload/initiate:
    post:
      operationId: initiate-media-upload
      summary: Start multipart media upload
      description: Begins a multipart upload for a video or audio file on a session. Requires `write:media`.
      tags: [Media]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filename, size_bytes]
              properties:
                filename:
                  type: string
                content_type:
                  type: string
                size_bytes:
                  type: integer
      responses:
        '201':
          description: Created
        '403':
          description: Missing write:media scope

  /v1/event/{eventId}/sessions/{sessionId}/media/upload/sign-part:
    post:
      operationId: sign-media-upload-part
      summary: Sign multipart upload parts
      description: Requires `write:media`.
      tags: [Media]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key, upload_id, part_numbers]
              properties:
                key:
                  type: string
                upload_id:
                  type: string
                part_numbers:
                  type: array
                  items:
                    type: integer
      responses:
        '200':
          description: OK
        '403':
          description: Missing write:media scope

  /v1/event/{eventId}/sessions/{sessionId}/media/upload/abort:
    post:
      operationId: abort-media-upload
      summary: Abort multipart upload
      description: Requires `write:media`.
      tags: [Media]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key, upload_id]
              properties:
                key:
                  type: string
                upload_id:
                  type: string
      responses:
        '200':
          description: OK
        '403':
          description: Missing write:media scope

  /v1/event/{eventId}/sessions/{sessionId}/media/upload/complete:
    post:
      operationId: complete-media-upload
      summary: Complete multipart media upload
      description: |
        Finalizes the upload and starts automatic transcription. When you upload on a session URL,
        transcript lines appear in the session transcription list when processing finishes.
        Requires `write:media`.
      tags: [Media]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key, upload_id, parts, filename, size_bytes]
              properties:
                key:
                  type: string
                upload_id:
                  type: string
                parts:
                  type: array
                  items:
                    type: object
                    required: [part_number, etag]
                    properties:
                      part_number:
                        type: integer
                      etag:
                        type: string
                filename:
                  type: string
                size_bytes:
                  type: integer
                duration_seconds:
                  type: number
      responses:
        '201':
          description: Created
        '403':
          description: Missing write:media scope

  /v1/event/{eventId}/sessions/{sessionId}/media/{mediaItemId}:
    get:
      operationId: get-media-item
      summary: Get media item status
      description: Check upload and transcription progress after completing an upload. Requires `read:media` (legacy tokens with empty scopes pass).
      tags: [Media]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - name: mediaItemId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MediaItemResponse'
        '403':
          description: Missing read:media scope
        '404':
          description: Not found

  /v1/event/{eventId}/sessions/{sessionId}/files:
    get:
      operationId: list-session-files
      summary: List files attached to a session
      description: |
        Returns completed files on the session (latest version per file group).
        Incomplete uploads (create/replace without complete) are omitted.
        Requires `read:sessions` scope (legacy tokens with empty scopes pass).
      tags: [Session Files]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionFileListResponse'
        '403':
          description: Missing read:sessions scope
        '404':
          description: Session not found
    post:
      operationId: create-session-file
      summary: Initiate session file upload
      description: |
        Creates a file record and returns a short-lived S3 upload URL.
        Requires `write:sessions`.

        Next: `PUT` the file bytes to `data.upload.url`, then call
        `POST .../files/{fileId}/complete`. For files up to **50 MB**, prefer
        `POST .../files/upload` (one multipart call). This direct-to-storage
        path supports up to **500 MB**.
        See [Uploading session files](/guides/uploading-session-files).
      tags: [Session Files]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionFileCreateRequest'
            examples:
              powerpoint:
                summary: PowerPoint slides
                value:
                  filename: keynote-slides.pptx
                  size_bytes: 2457600
                  content_type: application/vnd.openxmlformats-officedocument.presentationml.presentation
                  title: Keynote slides
              pdf:
                summary: PDF handout
                value:
                  filename: handout.pdf
                  size_bytes: 512000
                  content_type: application/pdf
                  title: Session handout
      responses:
        '201':
          description: Created — upload URL included
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionFileCreateResponse'
        '400':
          description: Validation error (missing fields, oversize, denied type)
        '403':
          description: Missing write:sessions scope

  /v1/event/{eventId}/sessions/{sessionId}/files/upload:
    post:
      operationId: upload-session-file
      summary: Upload a session file (simple)
      description: |
        Upload a file in one multipart request. Sessionboard detects size and
        MIME type from the bytes, runs security scanning, and returns the
        finalized file. Max **50 MB**. Requires `write:sessions`.

        For files larger than 50 MB (up to 500 MB), use the direct-to-storage
        flow: `POST .../files` → PUT to signed URL → `POST .../complete`.
        See [Uploading session files](/guides/uploading-session-files).
      tags: [Session Files]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: The file to attach (field name must be `file`).
                title:
                  type: string
                  description: Display title. Defaults to the uploaded filename.
                assigned_participant_id:
                  type: string
                  format: uuid
                  description: Contact UUID of a session participant.
      responses:
        '201':
          description: Created — file attached and scanned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionFileResponse'
        '400':
          description: Validation error (missing file, oversize, denied type)
        '403':
          description: Missing write:sessions scope

  /v1/event/{eventId}/sessions/{sessionId}/files/{fileId}/complete:
    post:
      operationId: complete-session-file
      summary: Finalize session file upload
      description: |
        Verifies the object in Sessionboard storage, runs security scanning
        (type deny-list + magic-byte checks; SVG sanitization when applicable),
        and finalizes the file on the session. Requires `write:sessions`.
      tags: [Session Files]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - $ref: '#/components/parameters/FileId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionFileResponse'
        '400':
          description: Upload missing, empty, or failed security checks
        '403':
          description: Missing write:sessions scope
        '404':
          description: File not found

  /v1/event/{eventId}/sessions/{sessionId}/files/{fileId}/replace:
    post:
      operationId: replace-session-file
      summary: Replace session file bytes
      description: |
        Issues a new upload URL for the same file record. After `PUT`ing the new
        bytes, call `complete` again. Requires `write:sessions`. Max **500 MB**
        (direct-to-storage flow).
      tags: [Session Files]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - $ref: '#/components/parameters/FileId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionFileCreateRequest'
      responses:
        '200':
          description: OK — new upload URL included
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionFileCreateResponse'
        '400':
          description: Validation error
        '403':
          description: Missing write:sessions scope
        '404':
          description: File not found

  /v1/event/{eventId}/sessions/{sessionId}/files/{fileId}:
    put:
      operationId: update-session-file
      summary: Update session file metadata
      description: |
        Updates display title and/or assigned participant. Does not change file
        bytes — use `replace` for that. Unknown fields are ignored.
        Requires `write:sessions`.
      tags: [Session Files]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - $ref: '#/components/parameters/FileId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SessionFileUpdateRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SessionFileResponse'
        '400':
          description: Validation error
        '403':
          description: Missing write:sessions scope
        '404':
          description: File not found
    delete:
      operationId: delete-session-file
      summary: Delete a session file
      description: Soft-deletes the file from the session. Requires `write:sessions`.
      tags: [Session Files]
      parameters:
        - $ref: '#/components/parameters/EventId'
        - $ref: '#/components/parameters/SessionId'
        - $ref: '#/components/parameters/FileId'
      responses:
        '204':
          description: No Content
        '403':
          description: Missing write:sessions scope
        '404':
          description: File not found

# ════════════════════════════════════════════════
# Components
# ════════════════════════════════════════════════
components:
  securitySchemes:
    ApiKey:
      type: apiKey
      in: header
      name: x-access-token
      description: Organization API token. Generate from Organization Settings → API Tokens.
    BearerToken:
      type: http
      scheme: bearer
      description: OAuth 2.1 access token. Obtained via the OAuth PKCE flow at `/oauth/token`.

  parameters:
    EventId:
      name: eventId
      in: path
      required: true
      schema:
        type: integer
      description: Event ID
    SessionId:
      name: sessionId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Session UUID
    FileId:
      name: fileId
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Session file (Content) UUID
    Page:
      name: page
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 999
        default: 1
      description: Page number for pagination.
    PageSize:
      name: pageSize
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 25
      description: Number of results per page.
    Expand:
      name: expand
      in: query
      required: false
      schema:
        type: array
        items:
          type: string
          enum:
            - translated_fields
            - subsession_details
            - linked_sources
            - composition
      description: |
        Expand records with additional data.

        Possible values:
        - `translated_fields` — includes the `translated_fields` array on records when the event has language variants configured.
        - `subsession_details` — applies to session responses. Each subsession inside `parent.subsessions[]` is returned with full parent-session field parity (`status`, `custom_status`, `custom_fields`, `chairpersons`, `moderators`, `sponsors`, `exhibitors`, `tags`, `language`, `track`, `level`, `room`, `is_public`, `external_url`, `client_session_id`, `ceu_credits`, `capacity`, `source`) instead of the default minimal shape. Subsessions inherit `is_abstract`, `composition_status`, and optional `composition` from the parent. Not required when calling `GET /v1/event/{eventId}/sessions/{sessionId}` with a subsession UUID — that endpoint always returns the full shape at the top level.
        - `linked_sources` — include sessions that are linked as composition sources (excluded from list/search results by default).
        - `composition` — attach a full `composition` object with target and source details on each returned session. Recommended for single-session GET; use cautiously on broad searches.

  schemas:
    # ────────────────────────────────────────────
    # Shared filter / sort helpers
    # ────────────────────────────────────────────
    DateRangeFilter:
      type: object
      description: Filter by a date range using before and/or after bounds.
      properties:
        before:
          type: string
          format: date-time
          description: Return records before this date-time.
        after:
          type: string
          format: date-time
          description: Return records after this date-time.

    SortOptions:
      type: object
      description: Sorting options for search results.
      properties:
        order:
          type: string
          enum:
            - createdAt
            - updatedAt
          description: The field to sort by.
        sort:
          type: string
          enum:
            - asc
            - desc
          description: Sort direction.

    RecordSearchBody:
      type: object
      description: Common search request body used across multiple endpoints.
      properties:
        filters:
          type: object
          properties:
            createdAt:
              $ref: '#/components/schemas/DateRangeFilter'
            updatedAt:
              $ref: '#/components/schemas/DateRangeFilter'
            status:
              type: string
              enum:
                - accepted
                - accept_queue
                - pending
                - decline_queue
                - declined
              description: Filter by session status (applicable to session searches).
        sort:
          $ref: '#/components/schemas/SortOptions'
        expand:
          type: array
          items:
            type: string
            enum:
              - translated_fields
              - subsession_details
              - linked_sources
              - composition
          description: |
            Expand records with additional data. See the `Expand` parameter for `linked_sources` and `composition` details.

    SessionSearchBody:
      type: object
      description: Search request body for sessions with status filtering.
      properties:
        filters:
          type: object
          properties:
            createdAt:
              $ref: '#/components/schemas/DateRangeFilter'
            updatedAt:
              $ref: '#/components/schemas/DateRangeFilter'
            status:
              type: string
              enum:
                - accepted
                - accept_queue
                - pending
                - decline_queue
                - declined
            isAbstract:
              type: boolean
              description: Filter to abstract submissions (`true`) or program sessions (`false`).
        sort:
          $ref: '#/components/schemas/SortOptions'
        expand:
          type: array
          items:
            type: string
            enum:
              - translated_fields
              - subsession_details
              - linked_sources
              - composition

    Pagination:
      type: object
      description: Pagination metadata returned with list responses.
      properties:
        currentPage:
          type: integer
          examples:
            - 1
        pageSize:
          type: integer
          examples:
            - 25
        totalPages:
          type: integer
          examples:
            - 4
        totalResults:
          type: integer
          examples:
            - 97

    # ────────────────────────────────────────────
    # Core domain objects
    # ────────────────────────────────────────────
    CompositionTargetSummary:
      type: object
      description: Summary of a related session in a composition relationship.
      properties:
        id:
          type: string
          format: uuid
        friendly_id:
          type: string
        title:
          type: string
        is_abstract:
          type: boolean

    CompositionStatus:
      type: object
      description: |
        Summary of how this session participates in a composition (merge or link).
        Always present on session responses.
      properties:
        role:
          type: string
          enum:
            - standalone
            - target
            - source
          description: |
            - `standalone` — normal editable record
            - `target` — composite parent with one or more linked sources
            - `source` — linked into another record (read-only)
        is_linked:
          type: boolean
        is_read_only:
          type: boolean
        source_count:
          type: integer
          description: Number of records merged or linked into this session.
        target:
          allOf:
            - $ref: '#/components/schemas/CompositionTargetSummary'
          nullable: true
          description: Present when `role` is `source`.

    CompositionSource:
      type: object
      properties:
        composition_id:
          type: string
          format: uuid
        relationship_type:
          type: string
          enum:
            - merged_session
            - merged_abstract
            - composed_into_session
        created_at:
          type: string
          format: date-time
        id:
          type: string
          format: uuid
        friendly_id:
          type: string
        title:
          type: string
        is_abstract:
          type: boolean

    CompositionDetail:
      type: object
      description: Full composition graph. Present when `expand=composition`.
      properties:
        target:
          allOf:
            - $ref: '#/components/schemas/CompositionTargetSummary'
          nullable: true
        sources:
          type: array
          items:
            $ref: '#/components/schemas/CompositionSource'

    ContactSessionSummary:
      type: object
      description: |
        Lightweight session summary for contact session lists. Includes
        composition summary fields but not the full `composition` graph.
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
        custom_status:
          type: string
          nullable: true
        is_abstract:
          type: boolean
        composition_status:
          $ref: '#/components/schemas/CompositionStatus'

    OrgContactSessionSummary:
      allOf:
        - $ref: '#/components/schemas/ContactSessionSummary'
        - type: object
          properties:
            event_id:
              type: integer
              description: Event the session belongs to.

    ContactSessionsResponse:
      type: object
      properties:
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/ContactSessionSummary'
        status:
          type: string
          description: |
            Aggregated contact status derived from linked sessions
            (`accepted`, `pending`, or a single session's status).

    OrgContactSessionsResponse:
      type: object
      properties:
        sessions:
          type: array
          items:
            $ref: '#/components/schemas/OrgContactSessionSummary'
        status:
          type: string
          description: |
            Aggregated contact status derived from linked sessions
            (`accepted`, `pending`, or a single session's status).

    Event:
      type: object
      description: An event on the Sessionboard platform.
      properties:
        id:
          type: integer
          examples:
            - 3960
        name:
          type: string
          examples:
            - "Acme Conference"
        timezone:
          type: string
          examples:
            - "America/Chicago"
        features:
          type: object
          properties:
            translated_fields:
              type: boolean

    Session:
      type: object
      description: A session within an event (e.g., talk, workshop, panel, or CFP abstract submission).
      properties:
        id:
          type: string
          format: uuid
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        title:
          type: string
        description:
          type: string
        status:
          type: string
          enum:
            - accepted
            - accept_queue
            - pending
            - decline_queue
            - declined
        custom_status_id:
          type: string
        custom_status:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        is_public:
          type: boolean
        is_abstract:
          type: boolean
          description: True for CFP abstract submissions; false for program sessions.
        composition_status:
          $ref: '#/components/schemas/CompositionStatus'
        composition:
          $ref: '#/components/schemas/CompositionDetail'
          description: Present when `expand=composition` is requested.
        external_url:
          type: string
          format: uri
        client_session_id:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        ceu_credits:
          type: number
          format: float
        capacity:
          type: integer
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'
        translated_fields:
          type: array
          items:
            $ref: '#/components/schemas/TranslatedField'
        speakers:
          type: array
          description: |
            Legacy junction-table speakers (`Session_Speakers`). Retained for backwards
            compatibility. Each entry includes `participant_role` when the event has
            Sessions 2.0 roles configured. Prefer `participants` for new integrations
            that need custom program roles or a single flat participant list.
          items:
            $ref: '#/components/schemas/SessionSpeaker'
        chairpersons:
          type: array
          description: |
            Legacy junction-table chairpersons (`Session_Chairpersons`). See `speakers`
            for migration guidance.
          items:
            $ref: '#/components/schemas/SessionSpeaker'
        moderators:
          type: array
          description: |
            Legacy junction-table moderators (`Session_Moderators`). See `speakers`
            for migration guidance.
          items:
            $ref: '#/components/schemas/SessionSpeaker'
        participants:
          type: array
          description: |
            Flat list of session participants from the Sessions 2.0 data model
            (`Session_Participants` + `Session_Roles`). Always returned on session
            search and get (no expand flag). Includes custom program roles (for example
            Author, Panelist) in addition to the legacy speaker/chairperson/moderator
            junction tables. Legacy arrays above remain for backwards compatibility;
            each legacy entry also includes `participant_role` when event roles are
            configured. Contact profile fields (`photo_url`, `company_name`, `title`,
            `address_country`, etc.) are hydrated from the contact record in the event's
            default language without requiring expand.
          items:
            $ref: '#/components/schemas/SessionParticipant'
        sponsors:
          type: array
          items:
            $ref: '#/components/schemas/SessionSponsor'
        exhibitors:
          type: array
          items:
            $ref: '#/components/schemas/SessionExhibitor'
        content:
          type: array
          items:
            $ref: '#/components/schemas/Content'
        tags:
          type: array
          items:
            $ref: '#/components/schemas/Tag'
        language:
          allOf:
            - $ref: '#/components/schemas/Language'
          description: |
            Assigned session language. Includes `id`, `event_id`, `name`, `order`,
            `created_at`, and `updated_at` when set. Unassigned values are an
            empty object `{}` on `POST` session search responses and `null` on
            CRUD proxy responses (`GET` list, create, update, restore).
        track:
          allOf:
            - $ref: '#/components/schemas/Track'
          description: |
            Assigned session track. Includes `id`, `event_id`, `name`, `color`,
            `order`, `created_at`, and `updated_at` when set. Unassigned values
            are `{}` on `POST` session search and `null` on CRUD proxy responses.
        level:
          allOf:
            - $ref: '#/components/schemas/Level'
          description: |
            Assigned session level. Includes `id`, `name`, `order`, `created_at`,
            and `updated_at` when set (no `event_id` on nested objects). Unassigned
            values are `{}` on `POST` session search and `null` on CRUD proxy responses.
        format:
          type: object
          description: |
            Assigned session format. Nested session objects include only `id` and
            `name` (not the full standalone `Format` schema).
          properties:
            id:
              type: string
            name:
              type: string
        room:
          allOf:
            - $ref: '#/components/schemas/Room'
          description: |
            Assigned room when `location` is `room`. Includes `id`, `name`,
            `order`, `capacity`, `created_at`, and `updated_at` when set. Omitted
            when location is `external` or unset (`{}` on `POST` search, `null` on
            CRUD proxy). `event_id` is not included on nested room objects.
        subsessions:
          type: array
          description: |
            Subsessions of this parent session. Each item defaults to the
            minimal `Subsession` shape; pass `expand=subsession_details` to
            receive the full `SubsessionDetailed` shape on every subsession
            in the array. Composition fields (`is_abstract`,
            `composition_status`, optional `composition`) are inherited from
            the parent session.
          items:
            oneOf:
              - $ref: '#/components/schemas/Subsession'
              - $ref: '#/components/schemas/SubsessionDetailed'
        admin_url:
          type: string
          format: uri
          nullable: true
          description: Deep link to this entity in the Sessionboard admin UI.
      examples:
        - id: "497f6eca-6276-4993-bfeb-53cbbbba6f08"
          friendly_id: "SESS-8"
          friendly_id_raw: 8
          title: "Networking"
          description: "<p>Networking session description</p>"
          status: "accepted"
          starts_at: "2024-03-15T09:00:00Z"
          ends_at: "2024-03-15T10:00:00Z"
          is_public: true
          created_at: "2024-01-10T14:30:00Z"
          updated_at: "2024-03-01T09:15:00Z"
          ceu_credits: 1.5
          capacity: 200
          language:
            id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
            event_id: 42
            name: "English"
            order: 0
            created_at: "2024-01-01T00:00:00Z"
            updated_at: "2024-01-01T00:00:00Z"
          track:
            id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
            event_id: 42
            name: "Artificial Intelligence & ML"
            color: "#329af0"
            order: 1
            created_at: "2024-01-01T00:00:00Z"
            updated_at: "2024-01-01T00:00:00Z"
          level:
            id: "c3d4e5f6-a7b8-9012-cdef-123456789012"
            name: "Beginner"
            order: 2
            created_at: "2024-01-01T00:00:00Z"
            updated_at: "2024-01-01T00:00:00Z"
          format:
            id: "d4e5f6a7-b8c9-0123-def0-234567890123"
            name: "Session"
          room:
            id: "e5f6a7b8-c9d0-1234-ef01-345678901234"
            name: "Workshop Room 1"
            order: 3
            capacity: 120
            created_at: "2024-01-01T00:00:00Z"
            updated_at: "2024-01-01T00:00:00Z"
          tags: []
          speakers: []
          content: []
          custom_fields: []
          admin_url: "https://app.sessionboard.com/event/42/sessions/497f6eca-6276-4993-bfeb-53cbbbba6f08"

    SessionStatus:
      type: object
      description: |
        A lightweight representation of a session's status, including
        soft-deleted sessions. Parent rows additionally include a
        `subsessions` array with the same minimal status-focused shape for
        each child subsession. Subsession rows that appear in the flat list
        return an empty `subsessions` array.
      properties:
        id:
          type: string
          format: uuid
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        status:
          type: string
        custom_status_id:
          type: string
          format: uuid
          nullable: true
        custom_status:
          $ref: '#/components/schemas/SessionCustomStatus'
        is_abstract:
          type: boolean
        composition_status:
          $ref: '#/components/schemas/CompositionStatus'
        deleted_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        subsessions:
          type: array
          description: Empty for subsession rows. Populated with `SubsessionStatus` items for parent rows.
          items:
            $ref: '#/components/schemas/SubsessionStatus'

    Contact:
      type: object
      description: A contact (speaker, attendee, or organizational contact) in Sessionboard.
      properties:
        id:
          type: string
        friendly_id:
          type: string
        full_name:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        created_at:
          type: string
        updated_at:
          type: string
        photo_url:
          type: string
        company_name:
          type: string
        title:
          type: string
        about:
          type: string
        phone_home:
          type: string
        phone_mobile:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type: string
        address_city:
          type: string
        address_state:
          type: string
        address_postal_code:
          type: string
        address_country:
          type: string
        website_url:
          type: string
        linkedin_url:
          type: string
        twitter_url:
          type: string
        facebook_url:
          type: string
        honorific:
          type: string
        salutation:
          type: string
        pronouns:
          type: string
        gender:
          type: string
        past_companies:
          type: string
        educational_affiliation:
          type: string
        highest_level_of_education:
          type: string
        ethnicity:
          type: string
        languages:
          type: string
        speaker_score:
          type: string
        organization_contact:
          type: string
        speaker_fee:
          type: string
        availability:
          type: string
        preferred_session_format:
          type: string
        annual_revenue:
          type: string
        headcount:
          type: string
        years_in_operation:
          type: string
        organization_structure:
          type: string
        industry:
          type: string
        global_region:
          type: string
        audience_type:
          type: string
        target_age_range:
          type: string
        topic_expertise:
          type: string
        brand:
          type: string
        translated_fields:
          type: array
          items:
            $ref: '#/components/schemas/TranslatedField'
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'
        admin_url:
          type: string
          format: uri
          nullable: true
          description: Deep link to this entity in the Sessionboard admin UI.
      examples:
        - id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          friendly_id: "SPK-42"
          full_name: "Dr. Jane Smith"
          first_name: "Jane"
          last_name: "Smith"
          email: "jane.smith@university.edu"
          created_at: "2023-11-01T10:00:00Z"
          updated_at: "2024-02-15T16:30:00Z"
          photo_url: "https://cdn.sessionboard.com/photos/jane-smith.jpg"
          company_name: "State University"
          title: "Professor of Computer Science"
          about: "Dr. Smith is a leading researcher in distributed systems."
          address_city: "Austin"
          address_state: "TX"
          address_country: "US"
          honorific: "Dr."
          pronouns: "she/her"
          topic_expertise: "Distributed Systems, Cloud Computing"
          custom_fields: []
          translated_fields: []

    SessionSpeaker:
      type: object
      description: |
        A contact embedded on a session (speaker, chairperson, moderator, or other
        program role). Standard contact profile fields (`photo_url`, `company_name`,
        `title`, `address_country`, etc.) are hydrated from the contact's module field
        values in the event's default language. No expand is required for these fields;
        use `expand=translated_fields` for other locales.
      properties:
        id:
          type: string
        participant_role:
          $ref: '#/components/schemas/ParticipantRole'
          description: |
            The event's configured program role for this participant (Sessions 2.0).
            `name` and `slug` reflect the configured `Session_Roles` record (for example
            Author, Panelist). `core_role` is the legacy junction category mapping only
            (`speaker`, `chairperson`, `moderator`) and must not be used as the display
            role label. Present on legacy `speakers`, `chairpersons`, and `moderators`
            entries when the event has `Session_Roles` configured.
        friendly_id:
          type: string
        full_name:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        created_at:
          type: string
        updated_at:
          type: string
        photo_url:
          type: string
        company_name:
          type: string
        title:
          type: string
        about:
          type: string
        phone_home:
          type: string
        phone_mobile:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type: string
        address_city:
          type: string
        address_state:
          type: string
        address_postal_code:
          type: string
        address_country:
          type: string
        website_url:
          type: string
        linkedin_url:
          type: string
        twitter_url:
          type: string
        facebook_url:
          type: string
        instagram_url:
          type: string
        snapchat_username:
          type: string
        tiktok_username:
          type: string
        translated_fields:
          type: array
          items:
            $ref: '#/components/schemas/TranslatedField'
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'

    ParticipantRole:
      type: object
      description: |
        An event-configured session participant role (`Session_Roles`). Use `name` (or
        `slug` for programmatic matching) as the participant's role label. `core_role`
        is the legacy junction category only.
      properties:
        id:
          type: string
          format: uuid
          description: Session_Roles.id
        slug:
          type: string
          description: Stable programmatic identifier for the role within the event.
        name:
          type: string
          description: Display name (singular).
        name_plural:
          type: string
          description: Display name (plural).
        core_role:
          type: string
          enum:
            - speaker
            - chairperson
            - moderator
          description: Legacy junction category this role maps to for integrations.

    SessionParticipant:
      allOf:
        - $ref: '#/components/schemas/SessionSpeaker'
        - type: object
          description: |
            Sessions 2.0 participant row. Includes the same contact profile fields as
            `SessionSpeaker`. `participant_role` is always present (sourced from
            `Session_Roles`).
          properties:
            session_participant_id:
              type: string
              format: uuid
              description: Primary key of the `Session_Participants` row.

    Sponsor:
      type: object
      description: A sponsor associated with an event.
      properties:
        id:
          type: string
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        name:
          type: string
        description:
          type: string
        logo_image_url:
          type: string
        banner_image_url:
          type: string
        phone:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type: string
        address_city:
          type: string
        address_state:
          type: string
        address_postal_code:
          type: string
        address_coutnry:
          type: string
          description: 'Country (note: field name contains a known typo preserved for backwards compatibility).'
        facebook_url:
          type: string
        instagram_url:
          type: string
        linkedin_url:
          type: string
        snapchat_username:
          type: string
        tiktok_username:
          type: string
        twitter_url:
          type: string
        website_url:
          type: string
        created_at:
          type: string
        updated_at:
          type: string
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'
        translated_fields:
          type: array
          items:
            $ref: '#/components/schemas/TranslatedField'
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/AccountContact'
        admin_url:
          type: string
          format: uri
          nullable: true
          description: Deep link to this entity in the Sessionboard admin UI.
      examples:
        - id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
          friendly_id: "SPON-3"
          friendly_id_raw: 3
          name: "TechCorp Global"
          description: "Platinum sponsor and technology partner."
          logo_image_url: "https://cdn.sessionboard.com/logos/techcorp.png"
          phone: "+1-555-0100"
          address_city: "San Francisco"
          address_state: "CA"
          address_coutnry: "US"
          website_url: "https://www.techcorp.example.com"
          linkedin_url: "https://www.linkedin.com/company/techcorp"
          created_at: "2024-01-05T08:00:00Z"
          updated_at: "2024-02-20T12:00:00Z"
          custom_fields: []
          translated_fields: []
          contacts: []

    SessionSponsor:
      type: object
      description: A sponsor associated with a session. Same shape as Sponsor.
      properties:
        id:
          type: string
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        name:
          type: string
        description:
          type: string
        logo_image_url:
          type: string
        banner_image_url:
          type: string
        phone:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type: string
        address_city:
          type: string
        address_state:
          type: string
        address_postal_code:
          type: string
        address_coutnry:
          type: string
        facebook_url:
          type: string
        instagram_url:
          type: string
        linkedin_url:
          type: string
        snapchat_username:
          type: string
        tiktok_username:
          type: string
        twitter_url:
          type: string
        website_url:
          type: string
        created_at:
          type: string
        updated_at:
          type: string
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'
        translated_fields:
          type: array
          items:
            $ref: '#/components/schemas/TranslatedField'
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/AccountContact'

    Exhibitor:
      type: object
      description: An exhibitor associated with an event.
      properties:
        id:
          type: string
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        name:
          type: string
        description:
          type: string
        logo_image_url:
          type: string
        banner_image_url:
          type: string
        phone:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type: string
        address_city:
          type: string
        address_state:
          type: string
        address_postal_code:
          type: string
        address_coutnry:
          type: string
          description: 'Country (note: field name contains a known typo preserved for backwards compatibility).'
        facebook_url:
          type: string
        instagram_url:
          type: string
        linkedin_url:
          type: string
        snapchat_username:
          type: string
        tiktok_username:
          type: string
        twitter_url:
          type: string
        website_url:
          type: string
        created_at:
          type: string
        updated_at:
          type: string
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'
        translated_fields:
          type: array
          items:
            $ref: '#/components/schemas/TranslatedField'
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/AccountContact'
        admin_url:
          type: string
          format: uri
          nullable: true
          description: Deep link to this entity in the Sessionboard admin UI.
      examples:
        - id: "c3d4e5f6-a7b8-9012-cdef-123456789012"
          friendly_id: "EXH-7"
          friendly_id_raw: 7
          name: "InnovateTech Labs"
          description: "Showcasing next-generation event technology solutions."
          logo_image_url: "https://cdn.sessionboard.com/logos/innovatetech.png"
          address_city: "Chicago"
          address_state: "IL"
          address_coutnry: "US"
          website_url: "https://www.innovatetech.example.com"
          created_at: "2024-01-12T10:00:00Z"
          updated_at: "2024-03-01T14:00:00Z"
          custom_fields: []
          translated_fields: []
          contacts: []

    SessionExhibitor:
      type: object
      description: An exhibitor associated with a session. Same shape as Exhibitor.
      properties:
        id:
          type: string
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        name:
          type: string
        description:
          type: string
        logo_image_url:
          type: string
        banner_image_url:
          type: string
        phone:
          type: string
        address_line_1:
          type: string
        address_line_2:
          type: string
        address_city:
          type: string
        address_state:
          type: string
        address_postal_code:
          type: string
        address_coutnry:
          type: string
        facebook_url:
          type: string
        instagram_url:
          type: string
        linkedin_url:
          type: string
        snapchat_username:
          type: string
        tiktok_username:
          type: string
        twitter_url:
          type: string
        website_url:
          type: string
        created_at:
          type: string
        updated_at:
          type: string
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'
        translated_fields:
          type: array
          items:
            $ref: '#/components/schemas/TranslatedField'
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/AccountContact'

    AccountContact:
      type: object
      description: A contact associated with a sponsor or exhibitor account.
      properties:
        id:
          type: string
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        full_name:
          type: string
        first_name:
          type: string
        last_name:
          type: string
        email:
          type: string
        photo_url:
          type: string
        company_name:
          type: string
        title:
          type: string
        about:
          type: string
        is_primary:
          type: boolean
        contact_created_at:
          type: string
        contact_updated_at:
          type: string
        relationship_created_at:
          type: string
        relationship_updated_at:
          type: string

    CustomFieldValue:
      type: object
      description: A custom field value attached to a record.
      properties:
        id:
          type: string
        name:
          type: string
        value:
          type: string
        type:
          type: string
        internal_name:
          type: string
        created_at:
          type: string

    TranslatedField:
      type: object
      description: A translated version of a field value.
      properties:
        id:
          type: string
        name:
          type: string
        type:
          type: string
        parent_field_id:
          type: string
        parent_field_source:
          type: string
        parent_field_name:
          type: string
        parent_field_internal_name:
          type: string
        translated_language:
          type: string
        value:
          type: string

    Content:
      type: object
      description: A content attachment (file, document, or media) associated with a session.
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        title:
          type: string
        filename:
          type: string
        size:
          type: integer
        mimetype:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        assigned_participant_id:
          type: string
          format: uuid
          nullable: true
          description: Contact UUID of the session participant this file is assigned to.
        assigned_participant_email:
          type: string
          format: email
          nullable: true
        assigned_participant_name:
          type: string
          nullable: true

    Tag:
      type: object
      description: A tag that can be applied to sessions.
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        created_at:
          type: string
        updated_at:
          type: string

    Track:
      type: object
      description: |
        A session track (e.g., a conference track or topic area).

        When nested inside a `Session` response, includes `event_id`. Standalone
        metadata list and search endpoints (`GET`/`POST /tracks`) omit `event_id`
        because the event is implicit from the URL.
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: integer
        name:
          type: string
        color:
          type: string
        order:
          type: integer
        created_at:
          type: string
        updated_at:
          type: string

    Language:
      type: object
      description: |
        A language configured for an event.

        When nested inside a `Session` response, includes `event_id`. Standalone
        metadata list and search endpoints (`GET`/`POST /languages`) omit
        `event_id` because the event is implicit from the URL.
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: integer
        name:
          type: string
        order:
          type: integer
        created_at:
          type: string
        updated_at:
          type: string

    Format:
      type: object
      description: A session format (e.g., Workshop, Panel, Keynote).
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: integer
        name:
          type: string
        order:
          type: integer
        created_at:
          type: string
        updated_at:
          type: string

    Level:
      type: object
      description: A session difficulty level (e.g., Beginner, Intermediate, Advanced).
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        order:
          type: integer
        created_at:
          type: string
        updated_at:
          type: string

    Room:
      type: object
      description: |
        A room or venue location for sessions. When nested inside a `Session`
        response, `room` is only populated when the session `location` is
        `room` (not for external URLs).
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        order:
          type: integer
        capacity:
          type: integer
          nullable: true
          description: |
            Maximum room capacity. Returned on nested session `room` objects and
            standalone room list/search endpoints.
        created_at:
          type: string
        updated_at:
          type: string

    Subsession:
      type: object
      description: |
        A subsession nested within a parent session. This is the default
        minimal shape returned inside `parent.subsessions[]` when the
        `subsession_details` expand value is not requested.

        To receive the full parent-shape parity (`status`, `custom_status`,
        `custom_fields`, `chairpersons`, `moderators`, `sponsors`,
        `exhibitors`, `tags`, `language`, `track`, `level`, `room`, etc.),
        pass `expand=subsession_details` on the GET or POST search request.
        See the `SubsessionDetailed` schema.

        `is_abstract`, `composition_status`, and optional `composition`
        (when `expand=composition`) are inherited from the parent session.

        Alternatively, call `GET /v1/event/{eventId}/sessions/{sessionId}`
        with the subsession's UUID directly — the endpoint always returns the
        full session shape at the top level.
      properties:
        id:
          type: string
          format: uuid
        friendly_id:
          type: string
          description: Human-readable id formatted as `<event-prefix>-<parent-friendly-id>-<subsession-number>` (or `<event-prefix>-<subsession-friendly-id>` when the event has `show_subsession_friendly_id` enabled).
        friendly_id_raw:
          type: integer
          description: Underlying numeric friendly id (the suffix of `friendly_id`).
        parent_session_friendly_id:
          type: string
          description: Human-readable friendly id of the parent session, e.g. `SESS-3370`.
        parent_session_friendly_id_raw:
          type: integer
          description: Numeric friendly id of the parent session.
        title:
          type: string
        description:
          type: string
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        translated_fields:
          type: array
          description: Present only when `expand=translated_fields` is set and the event has language variants configured.
          items:
            type: object
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/SessionSpeaker'
        participants:
          type: array
          description: Sessions 2.0 participants for this subsession (see `Session.participants`).
          items:
            $ref: '#/components/schemas/SessionParticipant'
        is_abstract:
          type: boolean
          description: Inherited from the parent session.
        composition_status:
          $ref: '#/components/schemas/CompositionStatus'
        format:
          type: object
          properties:
            id:
              type: string
            name:
              type: string
        content:
          type: array
          items:
            $ref: '#/components/schemas/Content'

    SubsessionDetailed:
      type: object
      description: |
        Full-parity subsession returned inside `parent.subsessions[]` when the
        `subsession_details` expand value is set on the request. The shape
        mirrors `Session` (minus the recursive `subsessions` property, since
        subsessions cannot be nested) and adds the parent-friendly-id fields
        so callers can iterate the array and then call
        `GET /v1/event/{eventId}/sessions/{sessionId}` with each subsession
        id for the same shape at the top level.

        `is_abstract`, `composition_status`, and optional `composition`
        (when `expand=composition`) are inherited from the parent session.
      properties:
        id:
          type: string
          format: uuid
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        parent_session_friendly_id:
          type: string
        parent_session_friendly_id_raw:
          type: integer
        title:
          type: string
        description:
          type: string
        status:
          type: string
        custom_status_id:
          type: string
          format: uuid
          nullable: true
        custom_status:
          $ref: '#/components/schemas/SessionCustomStatus'
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        is_public:
          type: boolean
        is_abstract:
          type: boolean
          description: Inherited from the parent session.
        composition_status:
          $ref: '#/components/schemas/CompositionStatus'
        external_url:
          type: string
          nullable: true
        client_session_id:
          type: string
          nullable: true
        ceu_credits:
          type: number
          nullable: true
        capacity:
          type: integer
          nullable: true
        translated_fields:
          type: array
          description: Present only when `expand=translated_fields` is set and the event has language variants configured.
          items:
            type: object
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        custom_fields:
          type: array
          items:
            $ref: '#/components/schemas/CustomFieldValue'
        speakers:
          type: array
          items:
            $ref: '#/components/schemas/SessionSpeaker'
        chairpersons:
          type: array
          items:
            $ref: '#/components/schemas/SessionSpeaker'
        moderators:
          type: array
          items:
            $ref: '#/components/schemas/SessionSpeaker'
        participants:
          type: array
          description: Sessions 2.0 participants for this subsession (see `Session.participants`).
          items:
            $ref: '#/components/schemas/SessionParticipant'
        sponsors:
          type: array
          items:
            $ref: '#/components/schemas/Sponsor'
        exhibitors:
          type: array
          items:
            $ref: '#/components/schemas/Exhibitor'
        content:
          type: array
          items:
            $ref: '#/components/schemas/Content'
        tags:
          type: array
          items:
            $ref: '#/components/schemas/Tag'
        language:
          allOf:
            - $ref: '#/components/schemas/Language'
          description: Same shape as `Session.language` when assigned; `{}` when unassigned.
        track:
          allOf:
            - $ref: '#/components/schemas/Track'
          description: Same shape as `Session.track` when assigned; `{}` when unassigned.
        level:
          allOf:
            - $ref: '#/components/schemas/Level'
          description: Same shape as `Session.level` when assigned; `{}` when unassigned.
        format:
          type: object
          description: Nested format includes only `id` and `name`.
          properties:
            id:
              type: string
            name:
              type: string
        room:
          allOf:
            - $ref: '#/components/schemas/Room'
          description: |
            Same shape as `Session.room` when `location` is `room`; `{}` when
            location is external or unset.
        source:
          type: string
      examples:
        - id: "ff14c2e4-2abf-4610-8267-0d2935f19576"
          friendly_id: "SESS-3370-3"
          friendly_id_raw: 3052
          parent_session_friendly_id: "SESS-3370"
          parent_session_friendly_id_raw: 3370
          title: "Imagining a better tomorrow"
          status: "accepted"
          is_public: true
          custom_status: null
          custom_fields:
            - id: "749c5737-cde4-44cc-acf6-15a6ccf74a5f"
              name: "Co-authors"
              type: "textarea"
              internal_name: "co_authors"
              value: "Mohamed Tarik, Mar..."
              created_at: "2026-01-21T13:15:57.710Z"
          chairpersons: []
          moderators: []
          sponsors: []
          exhibitors: []
          tags: []
          speakers: []
          content: []
          language:
            id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
            event_id: 72
            name: "English"
            order: 0
            created_at: "2025-01-01T00:00:00Z"
            updated_at: "2025-02-01T00:00:00Z"
          track:
            id: "b2c3d4e5-f6a7-8901-bcde-f12345678901"
            event_id: 72
            name: "Artificial Intelligence & ML"
            color: "#329af0"
            order: 1
            created_at: "2025-01-01T00:00:00Z"
            updated_at: "2025-02-01T00:00:00Z"
          level:
            id: "c3d4e5f6-a7b8-9012-cdef-123456789012"
            name: "Beginner"
            order: 2
            created_at: "2025-01-01T00:00:00Z"
            updated_at: "2025-02-01T00:00:00Z"
          format:
            id: "d4e5f6a7-b8c9-0123-def0-234567890123"
            name: "Technical Programme Forum Paper"
          room:
            id: "e5f6a7b8-c9d0-1234-ef01-345678901234"
            name: "Workshop Room 1"
            order: 3
            capacity: 120
            created_at: "2025-01-01T00:00:00Z"
            updated_at: "2025-02-01T00:00:00Z"
          source: "Manual"

    SubsessionStatus:
      type: object
      description: A lightweight representation of a subsession's status, included as part of the parent SessionStatus response.
      properties:
        id:
          type: string
          format: uuid
        friendly_id:
          type: string
        friendly_id_raw:
          type: integer
        parent_session_friendly_id:
          type: string
        parent_session_friendly_id_raw:
          type: integer
        status:
          type: string
          nullable: true
          description: |
            The subsession's own approval status from the database. The API does
            not inherit the parent session's status. May be null when the
            subsession has no explicit status set.
        custom_status_id:
          type: string
          format: uuid
          nullable: true
        custom_status:
          $ref: '#/components/schemas/SessionCustomStatus'
        deleted_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SessionCustomStatus:
      type: object
      description: A custom session status definition configured for an event.
      properties:
        id:
          type: string
        name:
          type: string
        status:
          type: string
          nullable: true
        status_name:
          type: string
          nullable: true
        color:
          type: string
        order:
          type: integer
        is_custom:
          type: boolean
        created_at:
          type: string
          nullable: true
        created_by_user_id:
          type: integer
          nullable: true
        created_by_user_name:
          type: string
          nullable: true

    GDPRRequest:
      type: object
      description: A GDPR data access or erasure request.
      properties:
        id:
          type: string
        org_id:
          type: string
        request_type:
          type: string
        identity_type:
          type: string
        identity_value:
          type: string
        status:
          type: string
        matching_user_exists:
          type: boolean
        response:
          type: object
          properties:
            users:
              type: array
              items:
                type: integer
            org_users:
              type: array
              items:
                type: string
            event_users:
              type: array
              items:
                type: integer
            contact_records:
              type: array
              items:
                type: string
        created_at:
          type: string
        updated_at:
          type: string

    Field:
      type: object
      description: A field definition (standard or custom) for an event module.
      properties:
        id:
          type: string
        internal_name:
          type: string
        public_name:
          type: string
        field_type:
          type: string
        field_source:
          type: string
        contains_pii:
          type: boolean
          description: Whether this field contains personally identifiable information (PII).
        scope:
          type: string
        createdAt:
          type: string
        updatedAt:
          type: string

    # ────────────────────────────────────────────
    # Bulk operations
    # ────────────────────────────────────────────
    BulkOperations:
      type: object
      description: Request body for bulk create/update/delete operations.
      required:
        - operations
      properties:
        operations:
          type: array
          maxItems: 100
          description: Array of operations to execute (max 100).
          items:
            type: object
            required:
              - action
            properties:
              action:
                type: string
                enum:
                  - create
                  - update
                  - delete
                description: The operation to perform.
              id:
                type: string
                format: uuid
                description: Entity ID. Required for `update` and `delete` actions.
              data:
                type: object
                description: |
                  Entity-specific fields. Required for `create` and `update` actions.
                  Session create supports `is_abstract` (boolean, default false).
                  `is_abstract` cannot be changed on update.

    BulkResponse:
      type: object
      description: Response body for bulk operations, including per-item results.
      properties:
        batch_id:
          type: string
          format: uuid
          description: Unique identifier for this bulk batch.
        results:
          type: array
          description: Per-operation results in the same order as the request.
          items:
            type: object
            properties:
              index:
                type: integer
                description: Zero-based index of the operation in the request array.
              action:
                type: string
                description: The action that was performed (create, update, delete).
              status:
                type: string
                enum:
                  - success
                  - error
                description: Whether this individual operation succeeded or failed.
              id:
                type: string
                format: uuid
                description: The ID of the created or affected entity.
              error:
                type: object
                description: Error details (only present when status is error).
                properties:
                  code:
                    type: string
                    description: Machine-readable error code.
                  message:
                    type: string
                    description: Human-readable error description.
        stats:
          type: object
          description: Summary statistics for the batch.
          properties:
            total:
              type: integer
              description: Total number of operations in the batch.
            succeeded:
              type: integer
              description: Number of operations that succeeded.
            failed:
              type: integer
              description: Number of operations that failed.

    AgendaDraft:
      type: object
      description: An agenda draft workspace for planning session schedule changes before committing them to the live event.
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: integer
        name:
          type: string
        description:
          type: string
        status:
          type: string
          enum:
            - draft
            - committed
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        admin_url:
          type: string
          format: uri
          nullable: true
          description: Deep link to this entity in the Sessionboard admin UI.

    DraftSession:
      type: object
      description: A session placement within an agenda draft.
      properties:
        id:
          type: string
          format: uuid
        draft_id:
          type: string
          format: uuid
        session_id:
          type: string
          format: uuid
        starts_at:
          type: string
          format: date-time
        ends_at:
          type: string
          format: date-time
        room_id:
          type: string
          format: uuid
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    EventRule:
      type: object
      description: A scheduling constraint or validation rule for an event.
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: integer
        name:
          type: string
        type:
          type: string
        config:
          type: object
          additionalProperties: true
        enabled:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    Persona:
      type: object
      description: An evaluation persona representing an attendee type for schedule evaluation.
      properties:
        id:
          type: string
          format: uuid
        event_id:
          type: integer
        name:
          type: string
        description:
          type: string
        config:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    TranscriptionResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/Transcription'

    TranscriptionListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Transcription'
        pagination:
          $ref: '#/components/schemas/Pagination'

    Transcription:
      type: object
      description: Polymorphic transcription artifact. Fields vary by type.
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum: [fragment, summary, insight, translation]
        session_id:
          type: string
          format: uuid
        transcription:
          type: string
        speaker:
          type: string
        is_partial:
          type: boolean
        provider:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    TranscriptionCreateRequest:
      type: object
      required: [type]
      properties:
        type:
          type: string
          enum: [fragment, summary, insight, translation]
        transcription:
          type: string

    Recording:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          example: recording
        session_id:
          type: string
          format: uuid
        s3_bucket:
          type: string
        s3_key:
          type: string
          description: After complete, non-PCM uploads are stored as `.pcm` keys.
        encoding:
          type: string
          example: linear16
        sample_rate:
          type: integer
          example: 16000
        channels:
          type: integer
          example: 1
        status:
          type: string
          enum: [recording, completed]
        duration_seconds:
          type: number
        file_size_bytes:
          type: integer
        started_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    RecordingResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/Recording'

    RecordingCreateResponse:
      type: object
      properties:
        data:
          allOf:
            - $ref: '#/components/schemas/Recording'
            - type: object
              properties:
                upload:
                  type: object
                  properties:
                    url:
                      type: string
                    method:
                      type: string
                      example: PUT
                    headers:
                      type: object
                      properties:
                        Content-Type:
                          type: string

    MediaItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          example: media_item
        title:
          type: string
        platform:
          type: string
        kind:
          type: string
        event_id:
          type: integer
        session_id:
          type: string
          format: uuid
        media_s3_key:
          type: string
        transcript_status:
          type: string
          enum: [queued, processing, ready, failed]
        duration_seconds:
          type: number
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    MediaItemResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/MediaItem'

    SessionFileCreateRequest:
      type: object
      required: [filename, size_bytes]
      properties:
        filename:
          type: string
          description: Original filename including extension (e.g. `slides.pptx`, `handout.pdf`).
          example: keynote-slides.pptx
        size_bytes:
          type: integer
          minimum: 1
          maximum: 524288000
          description: Exact file size in bytes. Maximum 500 MB (524288000) for direct-to-storage upload. Use `POST .../files/upload` for files up to 50 MB without specifying size.
        content_type:
          type: string
          description: MIME type. Optional; inferred from the filename extension when omitted.
          example: application/vnd.openxmlformats-officedocument.presentationml.presentation
        mimetype:
          type: string
          description: Alias for `content_type`.
        title:
          type: string
          description: Display title. Defaults to `filename`.
        assigned_participant_id:
          type: string
          format: uuid
          nullable: true
          description: Contact UUID of a session participant to assign this file to.

    SessionFileUpdateRequest:
      type: object
      properties:
        title:
          type: string
          nullable: true
        assigned_participant_id:
          type: string
          format: uuid
          nullable: true

    SessionFileResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/Content'

    SessionFileListResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Content'

    SessionFileCreateResponse:
      type: object
      properties:
        data:
          allOf:
            - $ref: '#/components/schemas/Content'
            - type: object
              properties:
                upload:
                  type: object
                  properties:
                    url:
                      type: string
                      format: uri
                      description: Presigned S3 URL — PUT the file bytes here.
                    method:
                      type: string
                      example: PUT
                    headers:
                      type: object
                      properties:
                        Content-Type:
                          type: string
