---
openapi: 3.0.1
info:
  title: TRMNL API
  version: '1'
  description: |
    Send an account API key, or the access token of an app a user connected with OAuth, as a bearer
    token. Create a key on your account settings page and pick what it may do: read,
    content, devices, delete, profile or apps; an app asks for the same scopes and the user
    picks them (see https://trmnl.com/auth.md). An operation outside
    them answers 403 naming the capability it needs. A key can also be limited to some
    devices and plugin settings, and then answers 403 naming what it was not granted. The
    legacy account API key reaches only the endpoints it always had, and answers 403 on the
    rest.

    This is a plain OpenAPI 3 document, so a client can be generated from it, or read
    straight off it at runtime. The TRMNL CLI (https://github.com/usetrmnl/cli) takes the second
    route, so every operation here is a command. It signs in through your browser, no key needed:

    ```
    brew install usetrmnl/tap/trmnl
    trmnl list-devices
    ```

    To build a plugin, use trmnlp instead: https://github.com/usetrmnl/trmnlp. It serves
    your markup locally with live reload and syncs it with your account.

    Over MCP (https://trmnl.com/mcp) an agent runs these same operations as account
    tools. An OAuth connection holds the capabilities its user ticked at consent (read,
    content, devices, delete, profile) and may be limited to some devices and plugin
    settings; an operation outside them answers 403 naming what is missing. See
    https://trmnl.com/auth.md.
paths:
  "/api/display":
    get:
      summary: Fetch the next screen
      operationId: getDisplay
      tags:
      - Device API
      x-cli-ignore: true
      parameters:
      - name: Access-Token
        in: header
        required: true
        description: Device API Key (eg. abc-123)
        schema:
          type: string
      - name: Battery-Voltage
        in: header
        required: false
        description: Device battery voltage (eg. 3.7)
        schema:
          type: number
      - name: Percent-Charged
        in: header
        required: false
        description: Device percent charged (eg. 69.4)
        schema:
          type: number
      - name: Battery-Count
        in: header
        required: false
        description: Number of batteries in device (e.g. 1-2)
        schema:
          type: number
      - name: Battery-Charging
        in: header
        required: false
        description: Whether device is plugged into power (eg. true)
        schema:
          type: number
      - name: Battery-Health
        in: header
        required: false
        description: Quality of battery (eg. -1-100)
        schema:
          type: number
      - name: Battery-Current
        in: header
        required: false
        description: Battery current (eg. -1-N)
        schema:
          type: number
      - name: Battery-Temp
        in: header
        required: false
        description: Battery temperature in celsius (eg. -1-N)
        schema:
          type: number
      - name: Battery-Capacity
        in: header
        required: false
        description: Ratio of current / max capacity (eg. -1/-1 in fault case)
        schema:
          type: number
      - name: USB-Connected
        in: header
        required: false
        description: Whether device is plugged into power via USB ("true" or "false")
        schema:
          type: string
      - name: WiFi-Band
        in: header
        required: false
        description: WiFi band the device is connected on ("2.4" or "5")
        schema:
          type: string
      - name: Panel-Rev
        in: header
        required: false
        description: Identifier of the ePaper panel fitted to the device
        schema:
          type: string
      - name: FW-Version
        in: header
        required: false
        description: Device firmware version (eg. 0.0.1)
        schema:
          type: string
      - name: FW-Commit
        in: header
        required: false
        description: Device firmware commit hash, for the development channel (eg.
          a14914c)
        schema:
          type: string
      - name: RSSI
        in: header
        required: false
        description: Device RSSI (eg. -69)
        schema:
          type: number
      - name: Height
        in: header
        required: false
        description: Device screen height (eg. 480)
        schema:
          type: string
      - name: Width
        in: header
        required: false
        description: Device screen width (eg. 800)
        schema:
          type: string
      - name: Special-Function
        in: header
        required: false
        description: Device special function (eg. true)
        schema:
          type: boolean
      - name: BASE64
        in: header
        required: false
        description: Encode image function (eg. true)
        schema:
          type: boolean
      - name: Sensors
        in: header
        required: false
        description: Environmental sensor data
        schema:
          type: string
      - name: Image-Cached
        in: header
        required: false
        description: Whether the device served the previous image from cache (eg.
          true / false)
        schema:
          type: string
      - name: Wake-Time
        in: header
        required: false
        description: Seconds the device was awake during the last cycle (eg. 12)
        schema:
          type: integer
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    example: 200
                  image_url:
                    type: string
                    nullable: true
                    example: https://trmnl.com/images/system_screens/setup_logo/og_plus.png
                  filename:
                    type: string
                    nullable: true
                    example: setup-logo.bmp
                  refresh_rate:
                    type: integer
                    example: 300
                  reset_firmware:
                    type: boolean
                    example: false
                  update_firmware:
                    type: boolean
                    example: false
                  firmware_url:
                    type: string
                    nullable: true
                    example: https://trmnl.com/firmware/1.0.0.bin
                  special_function:
                    type: string
                    nullable: false
                    example: identify
                  action:
                    type: string
                    nullable: true
                    example: identify
  "/api/display/current":
    get:
      summary: Fetch the current screen
      operationId: getCurrentScreen
      tags:
      - Device API
      x-cli-ignore: true
      parameters:
      - name: Access-Token
        in: header
        required: true
        description: Device API Key (eg. abc-123)
        schema:
          type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    example: 200
                  refresh_rate:
                    type: integer
                    example: 300
                  image_url:
                    type: string
                    nullable: true
                    example: https://trmnl.com/images/system_screens/setup_logo/og_plus.png
                  filename:
                    type: string
                    nullable: true
                    example: setup-logo.bmp
                  rendered_at:
                    type: string
                    nullable: true
                    example: '2023-01-01T00:00:00Z'
  "/api/log":
    post:
      summary: Log with logs[] (array)
      operationId: createDeviceLog
      tags:
      - Device API
      x-cli-ignore: true
      parameters:
      - name: Access-Token
        in: header
        required: true
        description: Device API Key (eg. abc-123)
        schema:
          type: string
      responses:
        '204':
          description: Logs created when ignore_log_messages is not set
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                logs:
                  type: array
                  items: {}
              required:
              - logs
              additionalProperties: false
        required: true
        description: 'An array of log entries. Each entry can be any JSON type: string,
          object, etc.'
  "/api/setup":
    get:
      summary: Set up device
      operationId: setupDevice
      tags:
      - Device API
      x-cli-ignore: true
      parameters:
      - name: ID
        in: header
        required: true
        description: Device MAC Address (eg. 41:B4:10:39:A1:24)
        schema:
          type: string
      - name: Model
        in: header
        required: true
        description: DEVICE_MODEL from firmware definitions
        schema:
          type: string
      - name: Panel-Rev
        in: header
        required: false
        description: Identifier of the ePaper panel fitted to the device
        schema:
          type: string
      description: |
        Please note that the returned `status` JSON value may NOT always equal the HTTP status code. Notably, if a device MAC address is not found,
        then the HTTP status code will be 200 but the `status` code in the response will be 404.
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    example: 200
                  api_key:
                    type: string
                    nullable: true
                    example: abc-123
                  friendly_id:
                    type: string
                    nullable: true
                    example: ABC-123
                  image_url:
                    type: string
                    nullable: true
                    example: https://trmnl.com/images/system_screens/setup_logo/og_plus.png
                  message:
                    type: string
                    example: Register at trmnl.com/start with Device ID 'ABC-123'
  "/api/categories":
    get:
      summary: List all plugin categories
      operationId: listCategories
      tags:
      - Categories
      description: Returns a list of approved plugin categories.
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: string
                    example:
                    - life
                    - marketing
                    - ecommerce
  "/api/firmware/flash":
    get:
      summary: List flashable firmware versions per device model
      operationId: listFlashFirmwares
      tags:
      - Flash Firmwares
      description: 'Firmware builds flashable via the TRMNL web flasher (/flash).

        '
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      models:
                        type: array
                        items:
                          type: object
                          properties:
                            keyname:
                              type: string
                            chipFamily:
                              type: string
                            label:
                              type: string
                            versions:
                              type: array
                              items:
                                type: object
                                properties:
                                  version:
                                    type: string
                                  url:
                                    type: string
                                required:
                                - version
                                - url
                          required:
                          - keyname
                          - chipFamily
                          - label
                          - versions
                    required:
                    - models
                required:
                - data
  "/api/ips":
    get:
      summary: List all TRMNL server IP addresses
      operationId: listServerIps
      tags:
      - Server IPs
      description: |
        Returns a list of public IP addresses for all TRMNL core servers.

        Plugin poll requests will only originate from these IPs.
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      ipv4:
                        type: array
                        items:
                          type: string
                      ipv6:
                        type: array
                        items:
                          type: string
  "/api/models":
    get:
      summary: List all device models
      operationId: listModels
      tags:
      - Models
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Model"
  "/api/palettes":
    get:
      summary: List all palettes
      operationId: listPalettes
      tags:
      - Palettes
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Palette"
  "/api/book/{token}/events":
    parameters:
    - name: token
      in: path
      description: The room public booking token, from its QR code or public_booking_token
      required: true
      schema:
        type: string
    get:
      summary: List the bookings a visitor sees
      tags:
      - Public booking
      operationId: listPublicBookingEvents
      description: 'Public: no bearer token, so it is not an account operation and
        no agent tool offers it. The rolling week from start_at, as the public booking
        page shows it, with times in the room time zone. A collection token also takes
        resource_type and resource_id to pick one of its rooms.'
      parameters:
      - name: start_at
        in: query
        required: true
        description: Window start, ISO 8601 or YYYY-MM-DDTHH:MM in the room time zone
        schema:
          type: string
      - name: resource_type
        in: query
        required: false
        enum:
        - ical
        - google
        - microsoft
        description: "Collection tokens only:\n * `ical` \n * `google` \n * `microsoft`
          \n "
        schema:
          type: string
      - name: resource_id
        in: query
        required: false
        description: Collection tokens only
        schema:
          type: integer
      responses:
        '410':
          description: 'Unknown token, Booking with TRMNL off, or a room that cannot
            be booked: all look the same'
        '400':
          description: start_at is missing
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PublicBookingEvent"
                  bookingLimit:
                    type: integer
                    description: How many bookings may overlap; more than one for
                      a desk pool
                  fetched_at:
                    type: string
                    format: date_time
                    nullable: true
  "/api/book/{token}/bookings":
    parameters:
    - name: token
      in: path
      description: The room public booking token
      required: true
      schema:
        type: string
    post:
      summary: Book a room as a visitor
      tags:
      - Public booking
      operationId: createPublicBooking
      description: 'Public: no bearer token. The same rules as the public booking
        page: within the room booking window, ending before midnight, and on an on-demand
        room duration_minutes (15, 30 or 60) from now instead of times. The title
        is capped and defaults when blank. Limited per token.'
      parameters:
      - name: resource_type
        in: query
        required: false
        description: Collection tokens only
        schema:
          type: string
      - name: resource_id
        in: query
        required: false
        description: Collection tokens only
        schema:
          type: integer
      responses:
        '410':
          description: 'Unknown token, Booking with TRMNL off, or a room that cannot
            be booked: all look the same'
        '200':
          description: Booked
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PublicBookingEvent"
        '409':
          description: The slot was just taken
        '422':
          description: Outside the booking window
        '429':
          description: Too many bookings from one token
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  nullable: true
                starts_at:
                  type: string
                  description: ISO 8601, or YYYY-MM-DDTHH:MM in the room time zone
                ends_at:
                  type: string
                duration_minutes:
                  type: integer
                  enum:
                  - 15
                  - 30
                  - 60
                  description: On-demand rooms only
        required: true
  "/api/book/{token}/bookings/{id}":
    parameters:
    - name: token
      in: path
      description: The room public booking token
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Booking id
      required: true
      schema:
        type: integer
    delete:
      summary: Cancel an upcoming public booking
      tags:
      - Public booking
      operationId: cancelPublicBooking
      description: 'Public: no bearer token. Only a booking made through TRMNL that
        has not started.'
      responses:
        '410':
          description: 'Unknown token, Booking with TRMNL off, or a room that cannot
            be booked: all look the same'
        '204':
          description: Canceled
        '404':
          description: A booking made outside TRMNL, or one on another room
  "/api/book/{token}/bookings/{id}/end":
    parameters:
    - name: token
      in: path
      description: The room public booking token
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Booking id
      required: true
      schema:
        type: integer
    patch:
      summary: End a public booking in progress
      tags:
      - Public booking
      operationId: endPublicBooking
      description: 'Public: no bearer token. Frees the room now; only a booking made
        through TRMNL that is in progress.'
      responses:
        '410':
          description: 'Unknown token, Booking with TRMNL off, or a room that cannot
            be booked: all look the same'
        '200':
          description: Ended
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PublicBookingEvent"
        '404':
          description: A booking that has not started
  "/api/analytics":
    get:
      summary: Read the analytics of the plugins you published
      tags:
      - Analytics
      operationId: getAuthorAnalytics
      description: Each recipe and third-party plugin you published with its installs,
        forks and health, the totals, the share of live installs in each health state
        and a 90-day install growth series.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      plugins:
                        type: array
                        items:
                          type: object
                          properties:
                            name:
                              type: string
                            state:
                              type: string
                              nullable: true
                              enum:
                              - healthy
                              - degraded
                              - erroring
                              -
                            installs:
                              type: integer
                            forks:
                              type: integer
                      stats:
                        type: object
                        properties:
                          plugins:
                            type: integer
                          connections:
                            type: integer
                          pageviews:
                            type: integer
                      health:
                        type: object
                        additionalProperties:
                          type: object
                          properties:
                            percent:
                              type: number
                              nullable: true
                        description: healthy, degraded and erroring as a share of
                          live installs
                      growth:
                        type: array
                        items:
                          type: array
                          items: {}
                          description: "[date, installs to date]"
  "/api/analytics/errors":
    get:
      summary: List the failing installs of the plugins you published
      tags:
      - Analytics
      operationId: listAuthorPluginErrors
      description: 'One row per distinct error message still failing in the last week,
        with who can act on it: author (your plugin), upstream (the data source),
        installer_connection or installer_setup. Rows you hid stay listed with hidden
        true.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        kind:
                          type: string
                          enum:
                          - recipe
                          - third_party
                        subject_id:
                          type: integer
                        plugin_name:
                          type: string
                          nullable: true
                        owner:
                          type: string
                          enum:
                          - author
                          - upstream
                          - installer_connection
                          - installer_setup
                        message:
                          type: string
                        count:
                          type: integer
                        last_seen:
                          type: string
                          format: date_time
                        hidden:
                          type: boolean
  "/api/analytics/hidden_errors":
    post:
      summary: Hide an error row from your analytics
      tags:
      - Analytics
      operationId: hideAuthorPluginError
      description: Takes the kind, subject_id, owner and message of a listAuthorPluginErrors
        row. The row stops showing on the dashboard and in the emails about it; unhideAuthorPluginError
        puts it back.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Hidden
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      hidden:
                        type: boolean
                        example: true
        '400':
          description: A field of the key is missing
        '422':
          description: A kind that is not recipe or third_party
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                kind:
                  type: string
                  enum:
                  - recipe
                  - third_party
                subject_id:
                  type: integer
                  description: The recipe or third-party plugin id of the row
                owner:
                  type: string
                  enum:
                  - author
                  - upstream
                  - installer_connection
                  - installer_setup
                message:
                  type: string
                  description: The row message; it is matched by shape, so numbers
                    and urls in it may differ
              required:
              - kind
              - subject_id
              - owner
              - message
        required: true
    delete:
      summary: Show a hidden error row again
      tags:
      - Analytics
      operationId: unhideAuthorPluginError
      description: Takes the same kind, subject_id, owner and message that hid the
        row.
      security:
      - bearer_auth: []
      parameters:
      - name: kind
        in: query
        required: true
        enum:
        - recipe
        - third_party
        schema:
          type: string
        description: ":\n * `recipe` \n * `third_party` \n "
      - name: subject_id
        in: query
        required: true
        schema:
          type: integer
      - name: owner
        in: query
        required: true
        enum:
        - author
        - upstream
        - installer_connection
        - installer_setup
        schema:
          type: string
      - name: message
        in: query
        required: true
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Shown again
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      hidden:
                        type: boolean
                        example: false
  "/api/analytics/uninstall_feedback":
    get:
      summary: Read why installers removed the plugins you published
      tags:
      - Analytics
      operationId: listUninstallFeedback
      description: 'The last 30 days of uninstall feedback across everything you published:
        the total, a count per reason, the latest written comments and the typical
        days installed. Never names the installer.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      window_days:
                        type: integer
                        example: 30
                      total:
                        type: integer
                      reason_breakdown:
                        type: array
                        items:
                          type: array
                          items: {}
                          description: "[reason, count]"
                      recent_comments:
                        type: array
                        items:
                          type: object
                          properties:
                            detail:
                              type: string
                            plugin_name:
                              type: string
                            reason_label:
                              type: string
                              nullable: true
                            kind_label:
                              type: string
                              nullable: true
                            created_at:
                              type: string
                              format: date_time
                      typical_days_installed:
                        type: integer
                        nullable: true
  "/api/apps/fleet/{installation_id}":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id, from listAppInstallations
      required: true
      schema:
        type: string
    get:
      summary: Read a fleet
      tags:
      - Apps - Fleet
      operationId: getFleet
      description: The master device, how many mirrors need a push, the settings mirrors
        inherit and the overdue alert. listFleetDevices has each member.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Fleet"
  "/api/apps/fleet/{installation_id}/devices":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id
      required: true
      schema:
        type: string
    get:
      summary: List the devices in a fleet
      tags:
      - Apps - Fleet
      operationId: listFleetDevices
      description: 'The master first, then the mirrors by urgency: overdue, on time,
        asleep, never seen.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/FleetMember"
    post:
      summary: Add a device to a fleet as a mirror
      tags:
      - Apps - Fleet
      operationId: addFleetDevice
      description: The device joins as a mirror; setFleetMaster promotes one. A device
        can be in one fleet only.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Device belongs to another user
        '200':
          description: Added
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/FleetMember"
        '422':
          description: The device is already in a fleet
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                device_id:
                  type: integer
              required:
              - device_id
        required: true
  "/api/apps/fleet/{installation_id}/devices/{device_id}":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id
      required: true
      schema:
        type: string
    - name: device_id
      in: path
      description: Device id
      required: true
      schema:
        type: integer
    delete:
      summary: Remove a device from a fleet
      tags:
      - Apps - Fleet
      operationId: removeFleetDevice
      description: Leaves the last pushed playlist on the device.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Device is not in this fleet
        '204':
          description: Removed
  "/api/apps/fleet/{installation_id}/devices/{device_id}/pushes":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id
      required: true
      schema:
        type: string
    - name: device_id
      in: path
      description: Device id of a mirror
      required: true
      schema:
        type: integer
    post:
      summary: Push the master playlist to one mirror
      tags:
      - Apps - Fleet
      operationId: pushFleetDevice
      description: Queues the push; the mirror shows the master playlist and inherited
        settings once it runs.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Not a mirror of this fleet
        '202':
          description: Queued
        '429':
          description: The mirror has had its hourly pushes
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/apps/fleet/{installation_id}/pushes":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id
      required: true
      schema:
        type: string
    post:
      summary: Push the master playlist to every mirror
      tags:
      - Apps - Fleet
      operationId: pushFleet
      description: Queues one push for the whole fleet. Each mirror gets a copy of
        the master playlist and the inherited settings.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '202':
          description: Queued
        '429':
          description: The fleet has had its hourly pushes
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: No master device
  "/api/apps/fleet/{installation_id}/settings":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id
      required: true
      schema:
        type: string
    patch:
      summary: Choose the settings mirrors inherit from the master
      tags:
      - Apps - Fleet
      operationId: updateFleetSettings
      description: Replaces the list. Each push copies these settings from the master;
        an empty list copies the playlist only.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Fleet"
        '422':
          description: A setting that cannot be inherited
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                inherited_settings:
                  type: array
                  items:
                    type: string
                    enum:
                    - refresh_interval
                    - orientation
                    - sleep
                    - palette
              required:
              - inherited_settings
        required: true
  "/api/apps/fleet/{installation_id}/alerts":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id
      required: true
      schema:
        type: string
    patch:
      summary: Change the overdue check-in alert
      tags:
      - Apps - Fleet
      operationId: updateFleetAlerts
      description: One email digest when a member is overdue by the threshold. Keys
        left out keep their value; a null email sends the digest to the account owner.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Fleet"
        '422':
          description: A threshold the alert does not offer
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                overdue_alert_enabled:
                  type: boolean
                overdue_alert_threshold_minutes:
                  type: integer
                  enum:
                  - 30
                  - 60
                  - 240
                overdue_alert_email:
                  type: string
                  nullable: true
        required: true
  "/api/apps/fleet/{installation_id}/master":
    parameters:
    - name: installation_id
      in: path
      description: Fleet installation id
      required: true
      schema:
        type: string
    put:
      summary: Set the master device
      tags:
      - Apps - Fleet
      operationId: setFleetMaster
      description: The device whose playlist and settings the mirrors copy. A mirror
        of this fleet is promoted; the previous master leaves the fleet.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Device belongs to another user
        '200':
          description: Set
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Fleet"
        '422':
          description: The device is in another fleet
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                device_id:
                  type: integer
              required:
              - device_id
        required: true
    delete:
      summary: Remove the master device
      tags:
      - Apps - Fleet
      operationId: removeFleetMaster
      description: The fleet keeps its mirrors but cannot push until a new master
        is set.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '204':
          description: Removed
  "/api/apps":
    get:
      summary: List the apps that can be installed
      tags:
      - Apps
      operationId: listApps
      description: Every app the platform offers, with whether this account holds
        an active installation of it.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/App"
  "/api/apps/installations":
    get:
      summary: List my app installations
      tags:
      - Apps
      operationId: listAppInstallations
      description: The active installations of this account. Their ids are the installation_id
        the fleet and room booking operations take.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/AppInstallation"
    post:
      summary: Install an app
      tags:
      - Apps
      operationId: installApp
      description: Installs the app for this account. An app already installed answers
        its existing installation with 200.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Installed, or already was
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/AppInstallation"
        '404':
          description: No such app
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                app_key:
                  type: string
                  description: One of the app_key values listApps answers
              required:
              - app_key
        required: true
  "/api/apps/installations/{id}":
    parameters:
    - name: id
      in: path
      description: Installation id
      required: true
      schema:
        type: string
    delete:
      summary: Uninstall an app
      tags:
      - Apps
      operationId: uninstallApp
      description: Retires the installation. Its calendars, fleet memberships and
        device bindings go with it.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Uninstalled
        '502':
          description: Stripe refused to cancel the subscription, so nothing was uninstalled
        '404':
          description: Not an installation id
  "/api/apps/room_booking/{installation_id}/integrations":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    get:
      summary: List the connected calendar accounts
      tags:
      - Apps - Room Booking
      operationId: listRoomBookingIntegrations
      description: The Google and Microsoft accounts (personal) and workspaces (admin)
        connected to the installation, with how many of their calendars are selected
        for sync. integration_type and id address one in the other integration operations.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/RoomBookingIntegration"
  "/api/apps/room_booking/{installation_id}/integrations/{integration_type}/connect_url":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: integration_type
      in: path
      enum:
      - google_user
      - google_workspace
      - microsoft_user
      - microsoft_workspace
      required: true
      schema:
        type: string
    get:
      summary: Where to send the owner to connect an account
      tags:
      - Apps - Room Booking
      operationId: getRoomBookingIntegrationConnectUrl
      description: 'Connecting needs the owner to consent at Google or Microsoft in
        a browser, signed in to TRMNL. The answer is the form action that starts it:
        an HTML form the owner submits with the given method, from a TRMNL page (it
        is CSRF-protected).'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Not an integration type
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      url:
                        type: string
                      method:
                        type: string
                      integrations_url:
                        type: string
  "/api/apps/room_booking/{installation_id}/integrations/{integration_type}/{id}/syncs":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: integration_type
      in: path
      enum:
      - google_user
      - google_workspace
      - microsoft_user
      - microsoft_workspace
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Integration id
      required: true
      schema:
        type: integer
    post:
      summary: Refresh the calendar list of a connected account
      tags:
      - Apps - Room Booking
      operationId: syncRoomBookingIntegration
      description: Reads the calendars the account can see from the provider, so a
        new room shows up in listRoomBookingCalendars once selected.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Integration of another type
        '200':
          description: Synchronized
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingIntegration"
        '429':
          description: The account has had its hourly syncs
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: The provider could not be read
  "/api/apps/room_booking/{installation_id}/integrations/{integration_type}/{id}":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: integration_type
      in: path
      enum:
      - google_user
      - google_workspace
      - microsoft_user
      - microsoft_workspace
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Integration id
      required: true
      schema:
        type: integer
    patch:
      summary: Choose the calendars of an account to sync, and its workspace settings
      tags:
      - Apps - Room Booking
      operationId: updateRoomBookingIntegration
      description: 'calendar_ids, when given, is the whole set to sync: a calendar
        left out stops syncing and leaves every screen and collection. The ids are
        the numbers of the account''s calendars, from listRoomBookingCalendars without
        the kind prefix. booking_organizer_email and parking_resource_group_id apply
        to a Microsoft workspace and are verified against Graph.'
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingIntegration"
        '422':
          description: Graph cannot verify the organizer mailbox
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                calendar_ids:
                  type: array
                  items:
                    type: integer
                booking_organizer_email:
                  type: string
                  nullable: true
                parking_resource_group_id:
                  type: string
                  nullable: true
        required: true
    delete:
      summary: Disconnect an account
      tags:
      - Apps - Room Booking
      operationId: disconnectRoomBookingIntegration
      description: Forgets the credentials and removes the account's calendars from
        every screen and collection.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Integration belongs to another installation
        '204':
          description: Disconnected
  "/api/apps/room_booking/{installation_id}":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id, from listAppInstallations
      required: true
      schema:
        type: string
    get:
      summary: Read a room booking installation
      tags:
      - Apps - Room Booking
      operationId: getRoomBooking
      description: What is connected, how many calendars and collections there are,
        which devices show one, and the billing state. Google and Microsoft accounts
        are connected in the browser.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingSummary"
  "/api/apps/room_booking/{installation_id}/billing":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    get:
      summary: Read the billing state of a room booking installation
      tags:
      - Apps - Room Booking
      operationId: getRoomBookingBilling
      description: Each calendar on a screen is billed monthly or yearly after a trial.
        Subscribing and managing the subscription happen in the browser on the billing
        page (checkout_url and portal_url), because each opens a Stripe session for
        the owner; the API never starts one.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: The billing state
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingBilling"
  "/api/apps/room_booking/{installation_id}/settings":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    get:
      summary: Read the settings of a room booking installation
      tags:
      - Apps - Room Booking
      operationId: getRoomBookingSettings
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingSettings"
    patch:
      summary: Change the settings of a room booking installation
      tags:
      - Apps - Room Booking
      operationId: updateRoomBookingSettings
      description: Keys left out keep their value. The company logo images are uploaded
        in the browser; show_company_logo only switches them on.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingSettings"
        '422':
          description: A time zone that does not exist
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                timezone:
                  type: string
                  nullable: true
                  description: IANA name for every calendar without its own; null
                    follows the owner
                public_bookings:
                  type: boolean
                  description: Booking with TRMNL
                dark_mode:
                  type: boolean
                show_company_logo:
                  type: boolean
                daily_public_booking_url_rotation:
                  type: boolean
                time_format:
                  type: string
                  nullable: true
                  enum:
                  -
                  - 24_hour
                  - 12_hour
                  description: null detects it from the time zone
                walk_up_calendar:
                  type: boolean
                  description: Upcoming bookings on the public booking page
                walk_up_booking_window_days:
                  type: integer
                  enum:
                  - 1
                  - 2
                  - 7
                  - 30
        required: true
  "/api/apps/room_booking/{installation_id}/settings/company_logo":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    put:
      summary: Upload the company logo shown on the screens
      tags:
      - Apps - Room Booking
      operationId: setRoomBookingCompanyLogo
      description: PNG, JPEG or SVG up to 2 MB, as JSON with the bytes in base64.
        Shown once show_company_logo is on.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Uploaded
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingSettings"
        '422':
          description: Not base64
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                image_base64:
                  type: string
                  description: The image bytes, base64 encoded
              required:
              - image_base64
        required: true
    delete:
      summary: Remove the company logo
      tags:
      - Apps - Room Booking
      operationId: removeRoomBookingCompanyLogo
      description: Removes the color logo with it, since the color one is only ever
        shown in its place.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '204':
          description: Removed
  "/api/apps/room_booking/{installation_id}/settings/color_company_logo":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    put:
      summary: Upload the color company logo for color screens
      tags:
      - Apps - Room Booking
      operationId: setRoomBookingColorCompanyLogo
      description: Takes the place of the company logo on a color device. Only accepted
        while the account has a color device or a color logo already uploaded.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Uploaded
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingSettings"
        '422':
          description: No color device on the account
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                image_base64:
                  type: string
              required:
              - image_base64
        required: true
    delete:
      summary: Remove the color company logo
      tags:
      - Apps - Room Booking
      operationId: removeRoomBookingColorCompanyLogo
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '204':
          description: Removed
  "/api/apps/room_booking/{installation_id}/calendars":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id, from listAppInstallations
      required: true
      schema:
        type: string
    get:
      summary: List the calendars of a room booking installation
      tags:
      - Apps - Room Booking
      operationId: listRoomBookingCalendars
      description: Every iCal calendar and every Google or Microsoft calendar selected
        for sync. A calendar id is "<kind>:<number>", kind being ical, google or microsoft,
        and is what the other calendar operations take.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/RoomBookingCalendar"
    post:
      summary: Add an iCal calendar
      tags:
      - Apps - Room Booking
      operationId: createRoomBookingCalendar
      description: 'An iCal feed is read-only: its events show on the screen but bookings
        are made in the source calendar. Google and Microsoft calendars are connected
        in the browser, since they need the owner to sign in.'
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '402':
          description: Billing is locked until the subscription is settled
        '200':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCalendar"
        '422':
          description: Refused
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                ics_url:
                  type: string
                  description: http, https or webcal
                timezone:
                  type: string
                  nullable: true
                  description: IANA name; the installation time zone when left out
                capacity:
                  type: integer
                  nullable: true
                show_booking_qr:
                  type: boolean
                show_company_logo:
                  type: boolean
              required:
              - name
              - ics_url
        required: true
  "/api/apps/room_booking/{installation_id}/calendars/{id}":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Calendar id, as listRoomBookingCalendars answers it
      required: true
      schema:
        type: string
    get:
      summary: Read a calendar with its current and upcoming bookings
      tags:
      - Apps - Room Booking
      operationId: getRoomBookingCalendar
      description: Answers the last-synced mirror of the source calendar (last_synced_at
        says how old); refetchRoomBookingCalendar reads the source again.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Not a calendar id
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCalendarDetails"
    patch:
      summary: Change a calendar
      tags:
      - Apps - Room Booking
      operationId: updateRoomBookingCalendar
      description: An iCal calendar takes name, ics_url, timezone, capacity, show_booking_qr
        and show_company_logo. A Google or Microsoft calendar keeps its provider name
        and takes override_name, show_booking_qr and show_company_logo.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCalendar"
        '422':
          description: Refused
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                ics_url:
                  type: string
                timezone:
                  type: string
                  nullable: true
                capacity:
                  type: integer
                  nullable: true
                override_name:
                  type: string
                  nullable: true
                  description: Google and Microsoft calendars only
                show_booking_qr:
                  type: boolean
                show_company_logo:
                  type: boolean
        required: true
    delete:
      summary: Remove an iCal calendar
      tags:
      - Apps - Room Booking
      operationId: deleteRoomBookingCalendar
      description: 'Takes it off every device. A Google or Microsoft calendar is not
        deleted here: deselect it from its account in the browser.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '204':
          description: Removed
        '422':
          description: A provider calendar
  "/api/apps/room_booking/{installation_id}/calendars/{id}/refetches":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Calendar id
      required: true
      schema:
        type: string
    post:
      summary: Refetch a calendar from its source now
      tags:
      - Apps - Room Booking
      operationId: refetchRoomBookingCalendar
      description: Reads the source calendar and updates the mirror the screens show.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Refetched
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCalendar"
        '429':
          description: The calendar has had its hourly refetches
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: The source could not be read
  "/api/apps/room_booking/{installation_id}/calendars/{id}/devices/{device_id}":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Calendar id
      required: true
      schema:
        type: string
    - name: device_id
      in: path
      schema:
        oneOf:
        - type: integer
        - type: string
          enum:
          - url
      description: Device ID, or url for the browser display
      required: true
    put:
      summary: Show a calendar on a device
      tags:
      - Apps - Room Booking
      operationId: bindRoomBookingCalendarDevice
      description: 'Adds the room screen to the device playlist once; a device already
        showing it is left as it is. The first room to reach a screen starts billing:
        subscription_required is then true and billing_url is where the owner subscribes,
        and until they do the other changes answer 402.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Device belongs to another user
        '402':
          description: Billing is locked until the subscription is settled
        '200':
          description: Showing
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCalendar"
                  subscription_required:
                    type: boolean
                  billing_url:
                    type: string
                    nullable: true
        '422':
          description: The account holds as many plugin settings as it may
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    delete:
      summary: Stop showing a calendar on a device
      tags:
      - Apps - Room Booking
      operationId: unbindRoomBookingCalendarDevice
      description: Removes the room screen from that device playlist only.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '204':
          description: Removed
  "/api/apps/room_booking/{installation_id}/calendars/{id}/screens":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Calendar id
      required: true
      schema:
        type: string
    get:
      summary: Read the screens a calendar is showing on
      tags:
      - Apps - Room Booking
      operationId: listRoomBookingCalendarScreens
      description: One entry per device showing this calendar, with the image that
        device is showing. A calendar on no device answers an empty list. screen is
        null while a device has not rendered the calendar yet, so a caller can tell
        that apart from not showing it at all. image_url is a presigned link (expires
        after ActiveStorage.service_urls_expire_in, 5 minutes by default); fetch it
        promptly.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Not a calendar of this installation
        '200':
          description: The devices showing it
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/RoomBookingDeviceScreen"
  "/api/apps/room_booking/{installation_id}/calendars/{id}/bookings":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Calendar id
      required: true
      schema:
        type: string
    get:
      summary: List the bookings of a calendar
      tags:
      - Apps - Room Booking
      operationId: listRoomBookings
      description: Confirmed bookings in a window, soonest first. The window defaults
        to the next seven days.
      security:
      - bearer_auth: []
      parameters:
      - name: from
        in: query
        format: date-time
        required: false
        description: Window start; now when left out
        schema:
          type: string
      - name: to
        in: query
        format: date-time
        required: false
        description: Window end; seven days after from when left out
        schema:
          type: string
      - name: status
        in: query
        enum:
        - confirmed
        - cancelled
        required: false
        description: "confirmed when left out:\n * `confirmed` \n * `cancelled` \n "
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/RoomBooking"
        '422':
          description: A window that ends before it starts
    post:
      summary: Book a room
      tags:
      - Apps - Room Booking
      operationId: createRoomBooking
      description: Books the slot on the room calendar as the owner. Times are ISO
        8601; a time without a zone is read in the calendar time zone. An iCal calendar
        is read-only and refuses; an overlap answers 409.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '402':
          description: Billing is locked until the subscription is settled
        '200':
          description: Booked
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBooking"
        '409':
          description: The slot overlaps a confirmed booking
        '422':
          description: Ends before it starts
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  nullable: true
                starts_at:
                  type: string
                  format: date-time
                ends_at:
                  type: string
                  format: date-time
              required:
              - starts_at
              - ends_at
        required: true
  "/api/apps/room_booking/{installation_id}/calendars/{id}/bookings/{booking_id}":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Calendar id
      required: true
      schema:
        type: string
    - name: booking_id
      in: path
      description: Booking id
      required: true
      schema:
        type: integer
    delete:
      summary: Cancel an upcoming booking
      tags:
      - Apps - Room Booking
      operationId: cancelRoomBooking
      description: Only a booking made through TRMNL that has not started yet; endRoomBooking
        is for one in progress.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: A booking already in progress
        '402':
          description: Billing is locked until the subscription is settled
        '204':
          description: Canceled
  "/api/apps/room_booking/{installation_id}/calendars/{id}/bookings/{booking_id}/end":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Calendar id
      required: true
      schema:
        type: string
    - name: booking_id
      in: path
      description: Booking id
      required: true
      schema:
        type: integer
    patch:
      summary: End a booking in progress now
      tags:
      - Apps - Room Booking
      operationId: endRoomBooking
      description: 'Frees the room: the booking ends at the current time. Only a booking
        made through TRMNL that is in progress.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: A booking that has not started
        '402':
          description: Billing is locked until the subscription is settled
        '200':
          description: Ended
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBooking"
  "/api/apps/room_booking/{installation_id}/collections":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    get:
      summary: List the calendar collections
      tags:
      - Apps - Room Booking
      operationId: listRoomBookingCollections
      description: A collection puts several calendars on one screen, as a list or
        a grid.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/RoomBookingCollection"
    post:
      summary: Add a calendar collection
      tags:
      - Apps - Room Booking
      operationId: createRoomBookingCollection
      description: resource_ids are calendar ids from listRoomBookingCalendars; at
        least one is needed.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '402':
          description: Billing is locked until the subscription is settled
        '200':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCollection"
        '422':
          description: A layout the collection does not have
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                collection_type:
                  type: string
                  enum:
                  - any
                  - rooms
                  - spaces
                  - desks
                  - desk_pools
                  - parking_spaces
                  - vehicles
                  - equipment
                  - people
                  - other
                  description: rooms when left out
                display_layout:
                  type: string
                  enum:
                  - list
                  - grid
                  description: list when left out
                show_booking_qr:
                  type: boolean
                show_company_logo:
                  type: boolean
                resource_ids:
                  type: array
                  items:
                    type: string
                  description: Calendar ids, as listRoomBookingCalendars answers them
              required:
              - name
              - resource_ids
        required: true
  "/api/apps/room_booking/{installation_id}/collections/{id}":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Collection id
      required: true
      schema:
        type: integer
    patch:
      summary: Change a calendar collection
      tags:
      - Apps - Room Booking
      operationId: updateRoomBookingCollection
      description: Keys left out keep their value; resource_ids, when given, replaces
        the whole set.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Collection belongs to another installation
        '402':
          description: Billing is locked until the subscription is settled
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCollection"
        '422':
          description: Emptying the calendars is refused, and the current ones stay
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                collection_type:
                  type: string
                  enum:
                  - any
                  - rooms
                  - spaces
                  - desks
                  - desk_pools
                  - parking_spaces
                  - vehicles
                  - equipment
                  - people
                  - other
                display_layout:
                  type: string
                  enum:
                  - list
                  - grid
                show_booking_qr:
                  type: boolean
                show_company_logo:
                  type: boolean
                resource_ids:
                  type: array
                  items:
                    type: string
        required: true
    delete:
      summary: Remove a calendar collection
      tags:
      - Apps - Room Booking
      operationId: deleteRoomBookingCollection
      description: Takes it off every device. The calendars in it stay.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '204':
          description: Removed
  "/api/apps/room_booking/{installation_id}/collections/{id}/devices/{device_id}":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Collection id
      required: true
      schema:
        type: integer
    - name: device_id
      in: path
      schema:
        oneOf:
        - type: integer
        - type: string
          enum:
          - url
      description: Device ID, or url for the browser display
      required: true
    put:
      summary: Show a collection on a device
      tags:
      - Apps - Room Booking
      operationId: bindRoomBookingCollectionDevice
      description: Adds the collection screen to the device playlist once. Billing
        works as for bindRoomBookingCalendarDevice.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Device belongs to another user
        '402':
          description: Billing is locked until the subscription is settled
        '200':
          description: Showing
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/RoomBookingCollection"
                  subscription_required:
                    type: boolean
                  billing_url:
                    type: string
                    nullable: true
    delete:
      summary: Stop showing a collection on a device
      tags:
      - Apps - Room Booking
      operationId: unbindRoomBookingCollectionDevice
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Installation belongs to another user
        '204':
          description: Removed
  "/api/apps/room_booking/{installation_id}/collections/{id}/screens":
    parameters:
    - name: installation_id
      in: path
      description: Room booking installation id
      required: true
      schema:
        type: string
    - name: id
      in: path
      description: Collection id
      required: true
      schema:
        type: integer
    get:
      summary: Read the screens a collection is showing on
      tags:
      - Apps - Room Booking
      operationId: listRoomBookingCollectionScreens
      description: The same answer as listRoomBookingCalendarScreens, for a collection.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Collection belongs to another installation
        '200':
          description: The devices showing it
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/RoomBookingDeviceScreen"
  "/api/plugin_installations":
    post:
      summary: Prepare an installation
      tags:
      - Installations
      operationId: prepareInstallation
      security:
      - bearer_auth: []
      parameters:
      - name: Idempotency-Key
        in: header
        required: true
        schema:
          type: string
          format: uuid
      - name: X-TRMNL-Configuration-Capabilities
        in: header
        required: false
        schema:
          type: string
          maxLength: 4096
      responses:
        '201':
          description: Created installation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginInstallation"
        '200':
          description: Retained installation replay
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginInstallation"
        '409':
          description: Idempotency key conflict
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ConfigurationError"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - kind
              - source_id
              - source_revision
              - choice_id
              properties:
                kind:
                  type: string
                  enum:
                  - official
                  - recipe
                source_id:
                  type: integer
                source_revision:
                  type: string
                choice_id:
                  type: string
                device_id:
                  type: integer
                  description: Device whose playlist gets the plugin once the installation
                    completes
        required: true
  "/api/plugin_installations/{id}":
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      summary: Read a retained installation
      tags:
      - Installations
      operationId: getInstallation
      security:
      - bearer_auth: []
      responses:
        '200':
          description: Installation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginInstallation"
    delete:
      summary: Cancel a pending installation
      tags:
      - Installations
      operationId: cancelInstallation
      security:
      - bearer_auth: []
      responses:
        '204':
          description: Cancelled
  "/api/plugin_installations/{plugin_installation_id}/completion":
    post:
      summary: Complete an installation once
      tags:
      - Installations
      operationId: completeInstallation
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_installation_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Completed installation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginInstallation"
        '422':
          description: Required setup is incomplete
  "/api/plugin_installations/{plugin_installation_id}/configuration":
    parameters:
    - name: plugin_installation_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      summary: Read pending configuration
      tags:
      - Installations
      operationId: getInstallationConfiguration
      security:
      - bearer_auth: []
      responses:
        '200':
          description: Pending configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    oneOf:
                    - "$ref": "#/components/schemas/PluginConfiguration"
                    - "$ref": "#/components/schemas/PluginInstallation"
    patch:
      summary: Save pending configuration
      tags:
      - Installations
      operationId: updateInstallationConfiguration
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '200':
          description: Updated pending configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConfiguration"
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ConfigurationWrite"
        required: true
  "/api/plugin_installations/{plugin_installation_id}/configuration/evaluation":
    post:
      summary: Evaluate pending configuration without saving
      tags:
      - Installations
      operationId: evaluateInstallationConfiguration
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_installation_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Evaluated configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConfiguration"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              - draft
              properties:
                revision:
                  type: string
                draft:
                  type: array
                  maxItems: 200
                  items:
                    "$ref": "#/components/schemas/ConfigurationChange"
        required: true
  "/api/plugin_installations/{plugin_installation_id}/configuration/choices/{resolver_id}":
    post:
      summary: Resolve pending configuration choices
      tags:
      - Installations
      operationId: getInstallationConfigurationChoices
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_installation_id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: resolver_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '404':
          description: Unknown resolver
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              - draft
              properties:
                revision:
                  type: string
                draft:
                  type: array
                  maxItems: 200
                  items:
                    "$ref": "#/components/schemas/ConfigurationChange"
                query:
                  type: string
                  maxLength: 200
                cursor:
                  type: string
        required: true
  "/api/plugin_settings/{plugin_setting_id}/removal_preview":
    get:
      summary: Preview installation removal
      tags:
      - Plugin Settings
      operationId: getPluginSettingRemovalPreview
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Affected placements
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    required:
                    - revision
                    - placements
                    properties:
                      revision:
                        type: string
                      placements:
                        type: array
                        items:
                          type: object
                          required:
                          - id
                          - device_id
                          - device_name
                          properties:
                            id:
                              type: integer
                            device_id:
                              type: integer
                            device_name:
                              type: string
                            impact:
                              type: string
                              enum:
                              - removed
                              - source_removed
  "/api/capabilities":
    get:
      summary: Read supported Companion contract versions
      tags:
      - Companion
      operationId: getCompanionCapabilities
      security:
      - bearer_auth: []
      responses:
        '200':
          description: Exact supported versions; missing features are unavailable
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    additionalProperties:
                      type: integer
  "/api/devices/{device_id}/association":
    delete:
      summary: Unlink an owned physical device
      tags:
      - Devices
      operationId: deleteDeviceAssociation
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '204':
          description: Device unlinked; the device record remains
  "/api/devices/{device_id}/mashups/options":
    get:
      summary: Read preset layouts and compatible plugin settings
      tags:
      - Mashups
      operationId: getMashupOptions
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        schema:
          type: integer
      - name: mashup_id
        in: query
        required: false
        schema:
          type: integer
      responses:
        '200':
          description: Options for this device
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/MashupOptions"
  "/api/playlists/items/{item_id}/preview":
    get:
      summary: Read the current rendered playlist image
      tags:
      - Playlists
      operationId: getPlaylistItemPreview
      security:
      - bearer_auth: []
      parameters:
      - name: item_id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Private image; no storage redirect or device check-in
  "/api/plugin_settings/{plugin_setting_id}/connections/{connection_id}/attempts":
    post:
      summary: Start provider authorization for the exact target
      tags:
      - Provider authorization
      operationId: startPluginSettingsConnection
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        schema:
          type: integer
      - name: connection_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '201':
          description: Created; launch_url is returned only on creation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConnectionAttempt"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - action_id
              - revision
              - callback_id
              properties:
                action_id:
                  type: string
                revision:
                  type: string
                callback_id:
                  type: string
                  enum:
                  - companion
        required: true
  "/api/plugin_settings/{plugin_setting_id}/connections/{connection_id}":
    delete:
      summary: Disconnect only this target provider grant
      tags:
      - Provider authorization
      operationId: disconnectPluginSettingsConnection
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        schema:
          type: integer
      - name: connection_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Configuration after disconnect
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConfiguration"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              properties:
                revision:
                  type: string
        required: true
  "/api/plugin_installations/{plugin_installation_id}/connections/{connection_id}/attempts":
    post:
      summary: Start provider authorization for the exact target
      tags:
      - Provider authorization
      operationId: startPluginInstallationsConnection
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_installation_id
        in: path
        required: true
        schema:
          type: string
      - name: connection_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '201':
          description: Created; launch_url is returned only on creation
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConnectionAttempt"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - action_id
              - revision
              - callback_id
              properties:
                action_id:
                  type: string
                revision:
                  type: string
                callback_id:
                  type: string
                  enum:
                  - companion
        required: true
  "/api/plugin_installations/{plugin_installation_id}/connections/{connection_id}":
    delete:
      summary: Disconnect only this target provider grant
      tags:
      - Provider authorization
      operationId: disconnectPluginInstallationsConnection
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_installation_id
        in: path
        required: true
        schema:
          type: string
      - name: connection_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '200':
          description: Configuration after disconnect
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConfiguration"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              properties:
                revision:
                  type: string
        required: true
  "/api/plugin_connection_attempts/{id}":
    get:
      summary: Read authoritative owned attempt state
      tags:
      - Provider authorization
      operationId: getConnectionAttempt
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Attempt state without launch ticket or provider data
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConnectionAttempt"
    delete:
      summary: Cancel an owned pending or exchanging attempt
      tags:
      - Provider authorization
      operationId: cancelConnectionAttempt
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Authoritative state; an already connected attempt remains connected
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConnectionAttempt"
  "/api/plugin_connection_attempts/{id}/confirmation":
    post:
      summary: Attach the exchanged grant with the confirmation from the provider
        callback
      tags:
      - Provider authorization
      operationId: confirmConnectionAttempt
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid
      responses:
        '200':
          description: Connected; the callback confirmation works once and only for
            the user who started the attempt
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConnectionAttempt"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - confirmation
              properties:
                confirmation:
                  type: string
        required: true
  "/api/devices":
    post:
      summary: Claim an unclaimed device
      tags:
      - Devices
      operationId: claimDevice
      description: Links a device that has not been claimed yet (a new device, or
        a QR code scan) to this account. Sets up the starter playlist item the first
        time. friendly_id is printed on the device screen during setup.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Claimed
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Device"
        '400':
          description: friendly_id is missing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: A concurrent claim already took the device
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          description: Too many failed claims from this account or address
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                friendly_id:
                  type: string
                  description: The code shown on the device during setup
              required:
              - friendly_id
        required: true
    get:
      summary: List my devices
      tags:
      - Devices
      operationId: listDevices
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Device"
  "/api/devices/{device_id}/coverage":
    get:
      summary: Times of the week a device's playlist shows nothing scheduled
      tags:
      - Devices
      operationId: getDevicePlaylistCoverage
      description: Windows carry minutes since midnight in the account timezone, end
        inclusive.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: The gaps in the schedule
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      gaps:
                        type: array
                        items:
                          type: object
                          properties:
                            week_days:
                              type: array
                              items:
                                type: integer
                              description: 0 is Sunday through 6 is Saturday
                            covered:
                              type: array
                              items:
                                type: array
                                items:
                                  type: integer
                              description: "[start_minute, end_minute] windows the
                                playlist shows"
                            uncovered:
                              type: array
                              items:
                                type: array
                                items:
                                  type: integer
                              description: "[start_minute, end_minute] windows nothing
                                is scheduled"
                      period_count:
                        type: integer
                        description: How many uncovered windows across the week
        '404':
          description: Device belongs to another user
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/devices/{device_id}/firmware_update_retries":
    post:
      summary: Retry a stalled firmware update
      tags:
      - Devices
      operationId: retryDeviceFirmwareUpdate
      description: Clears the backoff on a device stuck on an available firmware update,
        so the next check-in offers it again.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Backoff cleared
        '404':
          description: Device belongs to another user
  "/api/devices/{device_id}/forecast":
    get:
      summary: Simulate a device's coming check-ins, deciding each one the way a real
        check-in would
      tags:
      - Devices
      operationId: getDeviceForecast
      description: Simulates upcoming check-ins without writing anything. reason explains
        a step's wait (e.g. asleep, or an extended refresh rate).
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      - name: hours
        in: query
        required: false
        description: How far ahead to simulate, 1 to 48. Defaults to 24
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: The simulated steps
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      steps:
                        type: array
                        items:
                          type: object
                          properties:
                            at:
                              type: string
                              format: date_time
                              description: When this simulated check-in happens
                            playlist_item_id:
                              type: integer
                              nullable: true
                            plugin_setting_id:
                              type: integer
                              nullable: true
                            name:
                              type: string
                              nullable: true
                              description: The plugin setting or mashup showing at
                                this step
                            refresh_seconds:
                              type: integer
                              description: Seconds until the following step, after
                                any extension
                            refresh_rate_seconds:
                              type: integer
                              description: The item's own wake interval, before extension
                            reason:
                              type: string
                              nullable: true
                              description: Why the wait is what it is, e.g. asleep
                                or an extended refresh rate
                            render_at:
                              type: string
                              format: date_time
                              nullable: true
                              description: When the next render is predicted to land,
                                if any
                            render_reason:
                              type: string
                              nullable: true
                              description: Why no render is predicted, when render_at
                                is null
        '404':
          description: Device belongs to another user
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/devices/{device_id}/identification":
    post:
      summary: Identify a device
      tags:
      - Devices
      operationId: identifyDevice
      description: Requests a screen showing the device friendly ID. Rendering runs
        in the background. The device receives the screen when it next checks in;
        this does not wake a sleeping device.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '202':
          description: Identification requested
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                properties:
                  data:
                    type: object
                    required:
                    - success
                    properties:
                      success:
                        type: boolean
        '404':
          description: Device not found in your account
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Device could not be updated
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/devices/{device_id}/mirror":
    post:
      summary: Point a device at another device's shared playlist
      tags:
      - Devices
      operationId: mirrorDevice
      description: Mirroring copies the master device's sharable playlist onto this
        device. The master must have visibility set to sharable.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Mirroring the master
        '422':
          description: The friendly ID is not one this device can mirror
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '400':
          description: master_friendly_id is missing
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Device belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                master_friendly_id:
                  type: string
                  description: friendly_id of the sharable device to mirror
              required:
              - master_friendly_id
        required: true
    delete:
      summary: Stop mirroring
      tags:
      - Devices
      operationId: stopMirroringDevice
      description: Clears the device's mirror. This is a DELETE on the mirror link,
        not on the device — the device keeps its own playlist history.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Mirroring removed
        '404':
          description: Device belongs to another user
  "/api/devices/{device_id}/mirror/resyncs":
    post:
      summary: Resync a mirrored device with its master
      tags:
      - Devices
      operationId: resyncDeviceMirror
      description: Copies playlist items the master has added since the device last
        synced. Only playlist items are copied automatically otherwise; use this after
        adding new content to the master.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Resynced
        '404':
          description: Device belongs to another user
  "/api/devices/{device_id}/playlist_copies":
    post:
      summary: Copy a playlist to another device
      tags:
      - Playlists
      operationId: copyDevicePlaylist
      description: Appends items in source order without removing existing target
        items. Preserves appearance, priority, and schedules. Plugin instances are
        shared; mashups are copied. Existing setup placeholders for the same plugin
        are skipped. Both devices must belong to your account. Repeating this request
        adds the configured items again. Refreshes are queued after copying.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Source device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Playlist copied and refreshes queued
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                properties:
                  data:
                    type: object
                    required:
                    - success
                    properties:
                      success:
                        type: boolean
        '404':
          description: Source or destination device not found in your account
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Source and destination are the same, or an item is invalid
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '400':
          description: Destination device id is missing or is not a scalar
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - target_device_id
              properties:
                target_device_id:
                  type: integer
                  description: Destination device id
        required: true
  "/api/devices/{device_id}/playlist_items":
    get:
      summary: List the playlist for one device
      tags:
      - Playlists
      operationId: listDevicePlaylist
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Reads as empty for a device belonging to someone else
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PlaylistItem"
    post:
      summary: Add a plugin instance to a device playlist
      tags:
      - Playlists
      operationId: addDevicePlaylistItem
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Added to the playlist
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PlaylistItem"
        '404':
          description: Device belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                plugin_setting_id:
                  type: integer
                  description: Plugin setting id
              required:
              - plugin_setting_id
        required: true
  "/api/devices/{device_id}/playlist_items/order":
    put:
      summary: Reorder a device playlist
      tags:
      - Playlists
      operationId: reorderDevicePlaylist
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Reordered
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                        example: true
        '422':
          description: Ids do not name every item on the device
        '404':
          description: Device belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                playlist_item_ids:
                  type: array
                  items:
                    type: integer
              required:
              - playlist_item_ids
        required: true
  "/api/devices/{id}":
    parameters:
    - name: id
      in: path
      description: Device ID
      required: true
      schema:
        type: integer
    get:
      summary: Get the data of a device
      tags:
      - Devices
      operationId: getDevice
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Device"
        '404':
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    patch:
      summary: Update a device
      tags:
      - Devices
      operationId: updateDevice
      description: The same settings as the device page. Keys the device cannot take
        (custom dimensions on a fixed-size model, appearance on a native device) are
        dropped; a native device can only switch between the OG models.
      parameters: []
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          description: An appearance change renders every playlist item again, past
            the hourly render allowance
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Unprocessable Entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Device"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: Kitchen TRMNL
                refresh_interval:
                  type: integer
                  example: 900
                  description: Seconds between check-ins, 300 to 86400
                orientation:
                  type: integer
                  example: 0
                sleep_mode_enabled:
                  type: boolean
                  example: true
                sleep_screen_enabled:
                  type: boolean
                  example: true
                sleep_start_time:
                  type: integer
                  example: 1320
                  description: Minutes after midnight, 0 to 1439
                sleep_end_time:
                  type: integer
                  example: 480
                  description: Minutes after midnight, 0 to 1439
                sleep_until:
                  type: string
                  nullable: true
                  example: 2026-10-01T17:00
                  description: Sleep once, from the next check-in until this ISO 8601
                    time (at most a year out); no offset means the account time zone,
                    null cancels. Any later check-in, such as a button press, wakes
                    the device
                ota_enabled:
                  type: boolean
                  example: true
                low_battery_notification_enabled:
                  type: boolean
                  example: true
                low_battery_notification_email:
                  type: string
                  nullable: true
                low_battery_screen_disabled:
                  type: boolean
                percent_charged:
                  type: number
                  example: 69.0
                visibility:
                  type: string
                  enum:
                  - standalone
                  - sharable
                model_id:
                  type: integer
                  description: listModels; a BYOD device can take any model
                firmware_channel:
                  type: string
                  enum:
                  - development
                  - test
                  - staging
                  - production
                special_function:
                  type: string
                  nullable: true
                touchbar_mode:
                  type: string
                  enum:
                  - tap
                  - swipe
                temperature_profile:
                  type: string
                  enum:
                  - default
                  - a
                  - b
                waits_for_next_render:
                  type: boolean
                refreshing_screen_disabled:
                  type: boolean
                maximum_compatibility:
                  type: boolean
                guest_mode_item_id:
                  type: integer
                  nullable: true
                guest_mode_item_duration:
                  type: integer
                  nullable: true
                palette_id:
                  type: string
                  example: bw
                  description: listPalettes
                font_family:
                  type: string
                  example: classic
                text_scale:
                  type: string
                  enum:
                  - small
                  - regular
                  - large
                  - xlarge
                theme:
                  type: string
                auto_advance:
                  type: boolean
                  description: 'Beta: may change or go away. false keeps the current
                    screen until a button press; taken only when management.writable_fields
                    lists it'
                playlist_item_ttl:
                  type: integer
                  nullable: true
                  enum:
                  - 24
                  - 48
                  - 72
                  - 96
                  - 120
                  - 144
                  - 168
                  description: Hours a playlist item can go without a new screen before
                    it is skipped; null never skips
                custom_width:
                  type: integer
                  description: byod_custom model only
                custom_height:
                  type: integer
                  description: byod_custom model only
                custom_format:
                  type: string
                  description: byod_custom model only
                framework_size:
                  type: string
                  enum:
                  - sm
                  - md
                  - lg
                  description: byod_custom model only
                maximum_image_bytes:
                  type: integer
                  description: byod_custom model only
                dither_pixel_ratio:
                  type: number
                  description: byod_custom model only
                scale_factor:
                  type: number
                  description: BYOD only
                ui_scale:
                  type: number
                  description: BYOD only
                rotate:
                  type: integer
  "/api/devices/{device_id}/playlist":
    delete:
      summary: Clear a device playlist
      tags:
      - Playlists
      operationId: clearDevicePlaylist
      description: Removes every item from the device playlist. The plugin settings
        stay in the account.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Cleared
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                        example: true
        '404':
          description: Device belongs to another user
  "/api/devices/{device_id}/logs":
    get:
      summary: Read the logs of a device
      tags:
      - Devices
      operationId: getDeviceLogs
      description: What the device reported and what rendering for it logged, newest
        first. before_ts and before_event_id from the last row page further back.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      - name: level
        in: query
        required: false
        enum:
        - debug
        - info
        - warn
        - error
        schema:
          type: string
        description: ":\n * `debug` \n * `info` \n * `warn` \n * `error` \n "
      - name: source
        in: query
        required: false
        enum:
        - device
        - plugin
        - custom_plugin
        - private_plugin
        - mashup
        - support
        schema:
          type: string
      - name: limit
        in: query
        required: false
        description: 1 to 100, default 20
        schema:
          type: integer
      - name: before_ts
        in: query
        required: false
        schema:
          type: string
      - name: before_event_id
        in: query
        required: false
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Scopes the query to the device within the account
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      logs:
                        type: array
                        items:
                          type: object
                      more:
                        type: boolean
                      oldest_cursor:
                        type: object
                        nullable: true
                        properties:
                          ts:
                            type: string
                          event_id:
                            type: string
        '422':
          description: A level the logs do not have
        '404':
          description: Device belongs to another user
  "/api/markup":
    post:
      summary: Render Liquid template
      operationId: renderMarkup
      tags:
      - Markup
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
        '401':
          description: No API key or session
        '403':
          description: API key without the read capability
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                markup:
                  oneOf:
                  - type: string
                    example: Hello, {{ name }}!
                  - type: array
                    items:
                      type: string
                    example:
                    - Hello, {{ name }}!
                    - Goodbye, {{ name }}!
                variables:
                  type: object
                  example:
                    name: World
              required:
              - markup
              - variables
              additionalProperties: false
        required: true
        description: The Liquid markup(s) to render and an optional set of variables
          to use in the rendering process.
  "/api/devices/{device_id}/mashups":
    post:
      summary: Add a mashup to a device playlist
      tags:
      - Mashups
      operationId: createDeviceMashup
      description: Splits one screen between plugin settings. getDevice lists the
        layouts the device can show and the positions each layout has. The 1x1 layout
        adds a plain playlist item instead of a mashup.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Added to the playlist
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PlaylistItem"
        '422':
          description: A layout that does not exist
        '404':
          description: Device belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                layout:
                  type: string
                  enum:
                  - 1x1
                  - 1Tx1B
                  - 1Lx1R
                  - 1Tx2B
                  - 2Tx1B
                  - 1Lx2R
                  - 2Lx1R
                  - 2x2
                  - 3x3
                  description: One of the layouts getDevice lists for the device
                grid_config:
                  type: array
                  nullable: true
                  description: The cells of a 3x3 layout only
                  items:
                    type: object
                    properties:
                      pos:
                        type: string
                      col:
                        type: integer
                      row:
                        type: integer
                      cs:
                        type: integer
                      rs:
                        type: integer
                contents:
                  type: object
                  additionalProperties:
                    type: integer
                  description: Plugin setting id by position; a position left out
                    renders empty
              required:
              - layout
              - contents
        required: true
  "/api/mashups/{id}":
    get:
      summary: Read a mashup and its sections
      tags:
      - Mashups
      operationId: getMashup
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Mashup id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Mashup"
        '404':
          description: Mashup belongs to another user
    patch:
      summary: Change the sections of a mashup
      tags:
      - Mashups
      operationId: updateMashup
      description: Positions left out keep their plugin setting.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Mashup id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Mashup"
        '422':
          description: A position the layout does not have
        '404':
          description: Mashup belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                contents:
                  type: object
                  additionalProperties:
                    type: integer
                  description: Plugin setting id by position
                health_notification_enabled:
                  type: boolean
        required: true
  "/api/mashups/{id}/health_resets":
    post:
      summary: Reset a mashup past its health breaker
      tags:
      - Mashups
      operationId: resetMashupHealth
      description: Returns an erroring mashup to healthy and re-schedules its renders,
        the same as the reset link on the mashup page.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Mashup id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Healthy again
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                        example: true
        '404':
          description: Mashup belongs to another user
  "/api/me":
    get:
      summary: Get my user data
      operationId: getMe
      tags:
      - Users
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/User"
    patch:
      summary: Update my profile
      operationId: updateMe
      tags:
      - Users
      description: Name, time zone, locale and the account-wide display settings.
        Email, password and keys stay on the account page.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Takes a Rails zone name as well
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/User"
        '422':
          description: A locale the app does not have
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                first_name:
                  type: string
                last_name:
                  type: string
                time_zone:
                  type: string
                  example: Europe/London
                  description: An IANA name or a Rails zone name
                locale:
                  type: string
                  example: en
                title_bar_enabled:
                  type: boolean
                  description: Show the title bar on every screen
                low_battery_notification_email:
                  type: string
                  nullable: true
                account_name:
                  type: string
                  nullable: true
        required: true
  "/api/my_plugins":
    get:
      summary: List the third-party plugins you are building
      tags:
      - My Plugins
      operationId: listMyPlugins
      description: 'Plugins you author against the third-party plugin API, whatever
        their status: development, in_review or published. Recipes are plugin settings,
        not listed here.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/MyPlugin"
    post:
      summary: Start a third-party plugin
      tags:
      - My Plugins
      operationId: createMyPlugin
      description: Creates the plugin in development status; it publishes from the
        dashboard after review, not from here. The icon is an upload the dashboard
        takes. Needs a developer edition device on the account.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/MyPlugin"
        '422':
          description: A refresh rate the plugin API does not offer
        '403':
          description: No developer edition device on the account
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MyPluginParams"
        required: true
  "/api/my_plugins/{id}":
    patch:
      summary: Change a third-party plugin
      tags:
      - My Plugins
      operationId: updateMyPlugin
      description: Fields left out keep their value. Status cannot change here.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: A required field is blanked
        '404':
          description: Plugin belongs to another user
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/MyPlugin"
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/MyPluginParams"
        required: true
  "/api/plugin_settings/{plugin_setting_id}/configuration/photo_selection":
    parameters:
    - name: plugin_setting_id
      in: path
      required: true
      schema:
        type: integer
    post:
      summary: Start Google Photos selection
      tags:
      - Plugin Configuration
      operationId: startPhotoSelection
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '200':
          description: Picker URI and account-bound ticket
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    required:
                    - picker_uri
                    - ticket
                    - revision
                    properties:
                      picker_uri:
                        type: string
                      ticket:
                        type: string
                      revision:
                        type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              properties:
                revision:
                  type: string
        required: true
    patch:
      summary: Confirm Google Photos selection
      tags:
      - Plugin Configuration
      operationId: completePhotoSelection
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '200':
          description: Updated configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConfiguration"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              - ticket
              properties:
                revision:
                  type: string
                ticket:
                  type: string
        required: true
  "/api/devices/{device_id}/playlist_items/bulk":
    post:
      summary: Show or hide several playlist items on a device at once
      tags:
      - Playlists
      operationId: bulkUpdateDevicePlaylist
      description: action_type is hide or show. To remove items use deletePlaylistItem
        instead.
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Applied, answering the items in the submitted order
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PlaylistItem"
        '422':
          description: No named id belongs to the device
        '404':
          description: Device belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                action_type:
                  type: string
                  enum:
                  - hide
                  - show
                playlist_item_ids:
                  type: array
                  items:
                    type: integer
              required:
              - action_type
              - playlist_item_ids
        required: true
  "/api/playlists/items/{item_id}/schedule":
    get:
      summary: Read when a playlist item is allowed to display
      tags:
      - Playlists
      operationId: getPlaylistItemSchedule
      security:
      - bearer_auth: []
      parameters:
      - name: item_id
        in: path
        required: true
        description: Playlist item id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Reports an item with no windows as always active
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      week_schedules:
                        type: array
                        items:
                          type: object
                          properties:
                            week_days:
                              type: array
                              items:
                                type: integer
                              description: 0 is Sunday through 6 is Saturday
                            start_time:
                              type: string
                              example: '09:00'
                            end_time:
                              type: string
                              example: '17:00'
                      date_range:
                        type: object
                        nullable: true
                        description: Only display between these dates. Null when no
                          dates are set
                        properties:
                          start_on:
                            type: string
                            format: date
                            nullable: true
                            description: First day shown. Null for no start
                          end_on:
                            type: string
                            format: date
                            nullable: true
                            description: Last day shown. Null for no end
                          repeats_yearly:
                            type: boolean
                            description: Ignore the years and repeat every year. Needs
                              both dates, and a start after the end runs across the
                              new year
                      always_active:
                        type: boolean
                        example: false
                        description: True when no window and no dates are set, so
                          the item always displays
        '404':
          description: Playlist item belonging to another user
    put:
      summary: Replace when a playlist item is allowed to display
      tags:
      - Playlists
      operationId: replacePlaylistItemSchedule
      security:
      - bearer_auth: []
      parameters:
      - name: item_id
        in: path
        required: true
        description: Playlist item id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: An empty array and no date_range clear the schedule, so the
            item always displays
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      week_schedules:
                        type: array
                        items:
                          type: object
                          properties:
                            week_days:
                              type: array
                              items:
                                type: integer
                              description: 0 is Sunday through 6 is Saturday
                            start_time:
                              type: string
                              example: '09:00'
                            end_time:
                              type: string
                              example: '17:00'
                      date_range:
                        type: object
                        nullable: true
                        description: Only display between these dates. Null when no
                          dates are set
                        properties:
                          start_on:
                            type: string
                            format: date
                            nullable: true
                            description: First day shown. Null for no start
                          end_on:
                            type: string
                            format: date
                            nullable: true
                            description: Last day shown. Null for no end
                          repeats_yearly:
                            type: boolean
                            description: Ignore the years and repeat every year. Needs
                              both dates, and a start after the end runs across the
                              new year
                      always_active:
                        type: boolean
                        example: false
                        description: True when no window and no dates are set, so
                          the item always displays
        '422':
          description: A time that is not HH:MM is refused
        '404':
          description: Playlist item belonging to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                week_schedules:
                  type: array
                  items:
                    type: object
                    properties:
                      week_days:
                        type: array
                        items:
                          type: integer
                        description: 0 is Sunday through 6 is Saturday
                      start_time:
                        type: string
                        description: HH:MM, 00:00 to 23:59
                      end_time:
                        type: string
                        description: HH:MM, 00:00 to 23:59
                date_range:
                  type: object
                  nullable: true
                  description: Only display between these dates. Null when no dates
                    are set
                  properties:
                    start_on:
                      type: string
                      format: date
                      nullable: true
                      description: First day shown. Null for no start
                    end_on:
                      type: string
                      format: date
                      nullable: true
                      description: Last day shown. Null for no end
                    repeats_yearly:
                      type: boolean
                      description: Ignore the years and repeat every year. Needs both
                        dates, and a start after the end runs across the new year
              required:
              - week_schedules
        required: true
  "/api/playlists/items":
    get:
      summary: List my playlist items
      tags:
      - Playlists
      operationId: listPlaylistItems
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PlaylistItem"
  "/api/playlists/items/{id}":
    patch:
      summary: Update a playlist item
      tags:
      - Playlists
      operationId: updatePlaylistItem
      description: The appearance fields override the device's own for this item;
        null returns one to the device default. listPalettes names the palettes a
        device model has.
      parameters:
      - name: id
        in: path
        description: ID of the playlist item
        required: true
        schema:
          type: integer
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: item that is not an object is read as no change
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PlaylistItem"
        '422':
          description: An appearance value the device cannot show
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                visible:
                  type: boolean
                palette_id:
                  type: string
                  nullable: true
                  example: bw
                font_family:
                  type: string
                  nullable: true
                  example: classic
                text_scale:
                  type: string
                  nullable: true
                  enum:
                  - small
                  - regular
                  - large
                  - xlarge
                  -
                theme:
                  type: string
                  nullable: true
                  example: dark
    delete:
      summary: Remove a playlist item
      tags:
      - Playlists
      operationId: deletePlaylistItem
      parameters:
      - name: id
        in: path
        description: ID of the playlist item
        required: true
        schema:
          type: integer
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Removed
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                        example: true
        '404':
          description: Playlist item belonging to another user
  "/api/playlists/items/{id}/duplicates":
    post:
      summary: Duplicate a playlist item
      tags:
      - Playlists
      operationId: duplicatePlaylistItem
      description: Copies the plugin setting or mashup and places the copy on the
        same device.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Playlist item id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: The new playlist item
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PlaylistItem"
        '422':
          description: A placeholder has nothing to copy until it is configured
        '404':
          description: Playlist item belongs to another user
  "/api/plugin_settings/{id}/archive":
    parameters:
    - name: id
      in: path
      description: Plugin setting ID
      required: true
      schema:
        type: integer
    get:
      summary: Download a plugin setting archive
      tags:
      - Plugin Settings
      operationId: downloadPluginSettingArchive
      description: |
        Answers a zip, not JSON: settings.yml, one .liquid per markup size, and
        shared.liquid.

        This endpoint is available for unauthenticated requests.

        When unauthenticated, any published recipe may be archived.

        When authenticated, the requesting user's private plugins are also archivable.

        An archive of another user's recipe leaves polling_headers and polling_body empty.
      security:
      - bearer_auth: []
      responses:
        '200':
          description: Success
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Unprocessable Entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      summary: Upload a plugin setting archive
      tags:
      - Plugin Settings
      operationId: uploadPluginSettingArchive
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Unprocessable Entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginSettingArchive"
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: file
        required: true
        description: Plugin setting archive file
  "/api/plugin_settings/{plugin_setting_id}/configuration/evaluation":
    post:
      summary: Evaluate a typed draft without saving
      tags:
      - Plugin Configuration
      operationId: evaluatePluginConfiguration
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Evaluated configuration and safe field errors
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConfiguration"
        '409':
          description: Configuration changed
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              - draft
              properties:
                revision:
                  type: string
                draft:
                  type: array
                  items:
                    "$ref": "#/components/schemas/ConfigurationChange"
                  maxItems: 200
        required: true
  "/api/plugin_settings/{plugin_setting_id}/configuration/choices/{resolver_id}":
    post:
      summary: Read owner-bound remote choices
      tags:
      - Plugin Configuration
      operationId: getPluginConfigurationChoices
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        schema:
          type: integer
      - name: resolver_id
        in: path
        required: true
        schema:
          type: string
      responses:
        '404':
          description: Unknown resolver for owned target
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
              - revision
              - draft
              properties:
                revision:
                  type: string
                draft:
                  type: array
                  items:
                    "$ref": "#/components/schemas/ConfigurationChange"
                  maxItems: 200
                query:
                  type: string
                  maxLength: 200
                  nullable: true
                cursor:
                  type: string
                  nullable: true
        required: true
  "/api/plugin_settings/{plugin_setting_id}/configuration":
    get:
      summary: Read safe typed configuration
      tags:
      - Plugin Configuration
      operationId: getPluginConfiguration
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '401':
          description: Account bearer token required
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ConfigurationError"
        '200':
          description: Typed configuration with write-only secrets omitted
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginConfiguration"
        '404':
          description: Absent or unowned instance
    patch:
      summary: Apply atomic typed changes
      tags:
      - Plugin Configuration
      operationId: updatePluginConfiguration
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        schema:
          type: integer
      responses:
        '200':
          description: Configuration saved
        '409':
          description: Stale revision
        '422':
          description: Invalid typed change; no fields saved
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/ConfigurationWrite"
        required: true
  "/api/plugin_settings/custom_fields/verifications":
    post:
      summary: Check the custom fields YAML of a recipe
      tags:
      - Recipes
      operationId: verifyCustomFields
      description: Validates the YAML a recipe author writes under custom_fields,
        the form its installers fill in, without saving it. Each entry needs keyname,
        field_type and name.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Valid
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      valid:
                        type: boolean
                      errors:
                        type: array
                        items:
                          type: string
        '422':
          description: custom_fields that is not a string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                custom_fields:
                  type: string
                  description: The custom fields as a YAML list
              required:
              - custom_fields
        required: true
  "/api/plugin_settings/{id}/data":
    parameters:
    - name: id
      in: path
      description: Plugin setting ID or UUID
      required: true
      schema:
        type: string
    get:
      summary: Get the data of a native plugin setting; a private plugin reads through
        getMergeVariables
      operationId: getPluginSettingData
      tags:
      - Plugin Settings
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                properties:
                  data:
                    type: object
                    description: The merge variables the instance currently renders
                      from
        '404':
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: A private or global plugin has no data endpoint
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      summary: Update data for a webhook plugin setting
      operationId: updatePluginSettingData
      tags:
      - Plugin Settings
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success with UUID (no auth required)
          content:
            application/json:
              schema:
                type: object
                required:
                - error
                - merge_variables
                properties:
                  error:
                    type: string
                    nullable: true
                    description: Null when the write succeeded
                  merge_variables:
                    type: object
                    nullable: true
                    description: The variables now stored
                  processing:
                    type: string
                    example: serverless
                    description: Present when a transform runs out of band, so the
                      variables are not final
        '404':
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Data cannot be modified
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                merge_variables: {}
              required:
              - merge_variables
              additionalProperties: false
        required: true
        description: The value of `merge_variables` must be a JSON object
  "/api/plugin_settings/{id}/featured_image":
    put:
      summary: Generate the marketplace preview image of a plugin setting
      tags:
      - Plugin Settings
      operationId: setPluginSettingFeaturedImage
      description: Queues a fresh render of the instance and attaches it as the image
        the recipe listing shows. Takes no body. One generation runs at a time per
        instance; a request while one runs is answered the same way and queues nothing.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '202':
          description: Generation queued
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        enum:
                        - generating
        '404':
          description: Plugin setting belongs to another user
    delete:
      summary: Remove the marketplace preview image of a plugin setting
      tags:
      - Plugin Settings
      operationId: removePluginSettingFeaturedImage
      description: Deletes the featured image; the recipe listing then shows a screenshot
        instead.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Featured image removed
        '422':
          description: No featured image to remove
        '404':
          description: Plugin setting belongs to another user
  "/api/plugin_settings/{plugin_setting_id}/files":
    parameters:
    - name: plugin_setting_id
      in: path
      description: Plugin setting id
      required: true
      schema:
        type: integer
    get:
      summary: Read the files of a private plugin
      tags:
      - Plugin Settings
      operationId: getPluginSettingFiles
      description: 'The archive downloadPluginSettingArchive zips, as text by filename:
        settings.yml and one .liquid per markup size. Any published recipe can be
        read; another author''s recipe comes without its polling headers and body.'
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Reads another author's published recipe without its secrets
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      files:
                        type: object
                        additionalProperties:
                          type: string
        '404':
          description: Not the caller's and not a published recipe
        '422':
          description: A plugin type that has no files
    put:
      summary: Replace the files of a private plugin
      tags:
      - Plugin Settings
      operationId: importPluginSettingFiles
      description: The same files uploadPluginSettingArchive takes zipped, as text
        by filename. settings.yml is required; a markup file left out keeps its current
        content.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Persists a settings-only import, with no markup file to save
            through
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginSettingArchive"
        '422':
          description: settings.yml missing or malformed
        '404':
          description: Plugin setting belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                files:
                  type: object
                  additionalProperties:
                    type: string
                  example:
                    settings.yml: |
                      ---
                      strategy: static
                      name: Mine
                    full.liquid: "<div>{{ title }}</div>"
              required:
              - files
        required: true
  "/api/plugin_settings/{id}/image":
    parameters:
    - name: id
      in: path
      description: Plugin setting id or uuid
      required: true
      schema:
        type: string
    post:
      summary: Upload an image for a webhook_image plugin
      tags:
      - Plugin Settings
      operationId: uploadPluginSettingImage
      description: Send the image as JSON with the bytes in base64, or as the raw
        request body with its image Content-Type (not multipart form data). PNG, JPEG
        and WebP.
      parameters: []
      responses:
        '200':
          description: Full-color image on a color device
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      message:
                        type: string
        '422':
          description: Image too large
        '401':
          description: Numeric id without an API key
        '404':
          description: Not found
        '429':
          description: Rate limited
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                image_base64:
                  type: string
                  description: The image bytes, base64 encoded
              required:
              - image_base64
          image/png:
            schema:
              type: object
              properties:
                image_base64:
                  type: string
                  description: The image bytes, base64 encoded
              required:
              - image_base64
          image/jpeg:
            schema:
              type: object
              properties:
                image_base64:
                  type: string
                  description: The image bytes, base64 encoded
              required:
              - image_base64
          image/webp:
            schema:
              type: object
              properties:
                image_base64:
                  type: string
                  description: The image bytes, base64 encoded
              required:
              - image_base64
        required: true
  "/api/plugin_settings/{plugin_setting_id}/logs":
    get:
      summary: Read plugin instance health and recent logs
      tags:
      - Plugin Settings - Logs
      operationId: getPluginSettingLogs
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      - name: level
        in: query
        required: false
        enum:
        - debug
        - info
        - warn
        - error
        description: "Only return logs at this level:\n * `debug` \n * `info` \n *
          `warn` \n * `error` \n "
        schema:
          type: string
      - name: limit
        in: query
        required: false
        description: Maximum logs to return, 1 to 50. Defaults to 20
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Accepts the integer id the listing returns, as well as the
            uuid
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      health:
                        type: object
                        properties:
                          state:
                            type: string
                            example: active
                          error_message:
                            type: string
                            nullable: true
                          error_retry_count:
                            type: integer
                            nullable: true
                            example: 0
                          last_refresh:
                            type: string
                            format: date_time
                            nullable: true
                          next_refresh:
                            type: string
                            format: date_time
                            nullable: true
                      logs_status:
                        type: string
                        enum:
                        - available
                        - unavailable
                        description: Unavailable when the log store cannot be read;
                          logs is then empty
                      logs:
                        type: array
                        items:
                          type: object
                          properties:
                            event_id:
                              type: string
                              example: 11111111-1111-4111-8111-111111111111
                            ts:
                              type: string
                              example: '2026-09-05 12:00:00.000'
                            level:
                              type: string
                              example: error
                            source:
                              type: string
                              nullable: true
                              example: private_plugin
                            dump:
                              type: array
                              nullable: true
                              items:
                                type: object
                                properties:
                                  m:
                                    type: string
                                description: 'One log line, shaped { m: "<level>,
                                  <timestamp>, <message>" }'
        '422':
          description: A level the logs do not have
        '404':
          description: UUID belonging to another user
  "/api/plugin_settings/{plugin_setting_id}/markup/{size}":
    get:
      summary: Read markup for a size
      tags:
      - Plugin Settings - Markup
      operationId: readMarkup
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      - name: size
        in: path
        required: true
        description: Markup size
        schema:
          type: string
          enum:
          - markup_full
          - markup_half_horizontal
          - markup_half_vertical
          - markup_quadrant
          - markup_shared
          - tidbyt_canvas
          - transform_js
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Returns markup content
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      size:
                        type: string
                        example: markup_full
                      markup:
                        type: string
                        nullable: true
                        description: Null when nothing is attached at this size
                      shared_markup:
                        type: string
                        description: The markup_shared partial, returned alongside
                          any other size that has one
        '422':
          description: Invalid size
        '404':
          description: UUID belonging to another user
    put:
      summary: Write markup for a size
      tags:
      - Plugin Settings - Markup
      operationId: writeMarkup
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      - name: size
        in: path
        required: true
        description: Markup size
        schema:
          type: string
          enum:
          - markup_full
          - markup_half_horizontal
          - markup_half_vertical
          - markup_quadrant
          - markup_shared
          - tidbyt_canvas
          - transform_js
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Markup written successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                        example: true
                      size:
                        type: string
                        example: markup_full
        '422':
          description: Invalid size
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                content:
                  type: string
              required:
              - content
        required: true
  "/api/plugin_settings/{plugin_setting_id}/merge_variables":
    get:
      summary: Read the merge variables a plugin instance renders from
      tags:
      - Plugin Settings - Merge Variables
      operationId: getMergeVariables
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Infers the type of each variable held as static data
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      strategy:
                        type: string
                        nullable: true
                        example: polling
                      variables:
                        description: The merge variables the instance renders from
                      schema:
                        description: Type inferred for each merge variable
                      global_variables:
                        description: trmnl.* variables, with sensitive settings masked
                      transform_raw_input:
                        description: Pre-transform input, present only when a transform
                          is attached
                      transform_raw_input_schema:
                        description: Type inferred for each pre-transform variable
                      sensor_readings:
                        description: Present only when the owner has sensor readings
        '404':
          description: UUID belonging to another user
  "/api/plugin_settings/{id}":
    patch:
      summary: Rename a plugin setting or change how it refreshes
      tags:
      - Plugin Settings
      operationId: updatePluginSetting
      description: Changes the attributes of the instance itself. Its form fields
        are updatePluginSettingFields, its markup writeMarkup. A refresh_interval
        faster than the plan allows is raised to the nearest allowed value.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginSetting"
        '422':
          description: Pushes nothing to GitHub when the save is refused
        '404':
          description: Plugin setting belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                  nullable: true
                refresh_interval:
                  type: integer
                  enum:
                  - 1440
                  - 720
                  - 480
                  - 360
                  - 240
                  - 120
                  - 60
                  - 30
                  - 15
                  - 10
                  - 5
                  description: Minutes between renders
                health_notification_enabled:
                  type: boolean
                  description: Email the owner when the instance stops rendering
        required: true
    delete:
      summary: Delete a plugin setting
      tags:
      - Plugin Settings
      operationId: deletePluginSetting
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: ID of the plugin setting to delete
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Deleted
        '404':
          description: Not found
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/plugin_settings/{id}/copies":
    post:
      summary: Copy a plugin setting
      tags:
      - Plugin Settings
      operationId: copyPluginSetting
      description: Adds a new instance with the same settings and markup, named "[Copy]
        <name>", and answers it.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: The copy
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginSetting"
        '422':
          description: The copy does not pass validation
        '404':
          description: Plugin setting belongs to another user
  "/api/plugin_settings/{id}/state_clears":
    post:
      summary: Clear the state a plugin setting has accumulated
      tags:
      - Plugin Settings
      operationId: clearPluginSettingState
      description: Forgets the trmnl.state a private plugin carries between renders.
        Its data and settings stay.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: State cleared
        '404':
          description: Plugin setting belongs to another user
  "/api/plugin_settings/{id}/credentials":
    delete:
      summary: Forget the linked account and remove the plugin setting
      tags:
      - Plugin Settings
      operationId: resetPluginSettingCredentials
      description: 'Removes the OAuth credential the account holds for this plugin
        AND deletes the plugin setting, so the plugin can be set up again from scratch.
        Destructive: the setting is gone afterwards.'
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Credential forgotten and setting removed
        '422':
          description: A published recipe cannot be removed
        '404':
          description: Plugin setting belongs to another user
  "/api/plugin_settings/{id}/debug_logs":
    post:
      summary: Turn on debug logs for a day
      tags:
      - Plugin Settings - Logs
      operationId: enablePluginSettingDebugLogs
      description: A private plugin then records the request and response of each
        fetch for 24 hours; read them with getPluginSettingLogs.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Debug logs enabled
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      debug_logs_until:
                        type: string
                        format: date-time
        '422':
          description: Only a private plugin records debug logs
        '404':
          description: Plugin setting belongs to another user
  "/api/plugin_settings/{id}/health_resets":
    post:
      summary: Reset the health of a plugin setting that stopped rendering
      tags:
      - Plugin Settings
      operationId: resetPluginSettingHealth
      description: Clears the error count and puts the instance back on its render
        schedule. include_forks=true also resets every install of a recipe this setting
        publishes.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      - name: include_forks
        in: query
        required: false
        description: true also resets the installs of the recipe this setting publishes
        schema:
          type: boolean
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Health reset
        '404':
          description: Plugin setting belongs to another user
  "/api/plugin_settings/{id}/transform":
    delete:
      summary: Remove the transform script of a private plugin
      tags:
      - Plugin Settings - Markup
      operationId: removePluginSettingTransform
      description: Deletes the serverless transform file and forgets its language;
        renders then use the fetched data as is.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Transform removed
        '422':
          description: The setting refuses to save
        '404':
          description: Plugin setting belongs to another user
  "/api/plugin_settings/{plugin_setting_id}/refreshes":
    post:
      summary: Refresh a plugin instance
      tags:
      - Plugin Settings - Refresh
      operationId: startRefresh
      description: A polling private plugin refetches its URL and answers a job_id
        to poll with getRefresh. Any other instance queues a render of its current
        data and answers without one.
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '202':
          description: Queues a render for an instance that does not poll, such as
            a native plugin
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      job_id:
                        type: string
                        example: 3f1e...
                        description: Only for a polling instance
                      status:
                        type: string
                        enum:
                        - pending
                        - queued
                        example: pending
                      render_in_progress:
                        type: boolean
                        description: 'Only for a render: another render already holds
                          the instance'
        '429':
          description: The account has used its hourly render allowance, shared with
            the dashboard and MCP
        '422':
          description: Instance has no polling URL
        '404':
          description: UUID belonging to another user
  "/api/plugin_settings/{plugin_setting_id}/refreshes/{id}":
    get:
      summary: Read the result of a refresh
      tags:
      - Plugin Settings - Refresh
      operationId: getRefresh
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      - name: id
        in: path
        required: true
        description: The job_id returned when the refresh was started
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '202':
          description: Refresh has not finished yet
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      job_id:
                        type: string
                        example: 3f1e...
                      status:
                        type: string
                        enum:
                        - pending
                        example: pending
        '410':
          description: Result aged out, or the job id was never issued
        '200':
          description: Returns the freshly fetched variables
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      job_id:
                        type: string
                      status:
                        type: string
                        enum:
                        - complete
                        example: complete
                      success:
                        type: boolean
                        example: true
                      variables:
                        description: The merge variables the fetch returned
                      schema:
                        description: Type inferred for each merge variable
                      polling_url:
                        type: string
                        nullable: true
                      warning:
                        type: string
                        description: Present when the data is stale after a transform
                          failure
                      logs:
                        type: array
                        items:
                          description: A log entry from the fetch
        '404':
          description: UUID belonging to another user
  "/api/plugin_settings/{plugin_setting_id}/screenshots":
    post:
      summary: Start a preview render of a plugin instance
      tags:
      - Plugin Settings - Preview
      operationId: startPreview
      description: |
        Renders the instance so you can check its layout. The image comes back at reduced
        resolution and is not sized for a panel; a device is driven through /api/display.
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '202':
          description: Renders one reduced image per request, however many the MCP
            tools batch at once
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      job_id:
                        type: string
                      status:
                        type: string
                        enum:
                        - pending
                        example: pending
        '422':
          description: Unknown device model
        '429':
          description: The account has used its hourly render allowance, shared with
            the dashboard and MCP
        '404':
          description: UUID belonging to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                size:
                  type: string
                  example: markup_full
                  enum:
                  - markup_full
                  - markup_half_horizontal
                  - markup_half_vertical
                  - markup_quadrant
                device_model:
                  type: string
                  nullable: true
                  example: og
                  description: Device model keyname to render on. Omit for the standard
                    preview appearance
  "/api/plugin_settings/{plugin_setting_id}/screenshots/{id}":
    get:
      summary: Read the result of a preview render
      tags:
      - Plugin Settings - Preview
      operationId: getPreview
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      - name: id
        in: path
        required: true
        description: The job_id returned when the render was started
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '202':
          description: Render has not finished yet
        '200':
          description: Hands back the image the worker rendered
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      job_id:
                        type: string
                      status:
                        type: string
                        enum:
                        - complete
                        example: complete
                      preview_base64:
                        type: string
                        description: A reduced-resolution PNG for checking layout,
                          not a device-ready image
        '410':
          description: Result aged out, or the job id was never issued
        '422':
          description: The render failed
        '404':
          description: UUID belonging to another user
  "/api/plugin_settings/{plugin_setting_id}/settings":
    patch:
      summary: Update plugin settings fields
      tags:
      - Plugin Settings - Settings
      operationId: updatePluginSettingFields
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Settings updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      success:
                        type: boolean
                        example: true
                      warnings:
                        type: array
                        items:
                          type: string
                        description: Fields that were written but are hidden under
                          the current settings
        '422':
          description: Non-string field values
        '404':
          description: Unknown UUID
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                fields:
                  type: object
              required:
              - fields
        required: true
  "/api/plugin_settings":
    get:
      summary: List my plugin settings
      tags:
      - Plugin Settings
      operationId: listPluginSettings
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_id
        in: query
        required: false
        description: ID of a plugin or "calendars" to filter calendar plugins
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Returns all calendar plugin settings except Google Calendar
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/PluginSetting"
    post:
      summary: Create a new plugin setting
      tags:
      - Plugin Settings
      operationId: createPluginSetting
      description: listPlugins gives the plugin_id to send here; getPlugin lists the
        fields to send updatePluginSettingFields next.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/PluginSetting"
        '422':
          description: Unprocessable Entity
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
      requestBody:
        content:
          application/json:
            schema:
              "$ref": "#/components/schemas/PluginSettingParams"
        required: true
  "/api/plugin_settings/{id}/details":
    get:
      summary: Get plugin setting details
      tags:
      - Plugin Settings
      operationId: getPluginSettingDetails
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin setting id or uuid
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Returns plugin details with sizes
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      name:
                        type: string
                        example: My Plugin Setting
                      plugin_name:
                        type: string
                        example: Private Plugin
                      state:
                        type: string
                        example: active
                      strategy:
                        type: string
                        nullable: true
                        example: polling
                      transform_runtime:
                        type: string
                        example: serverless
                      serverless_language:
                        type: string
                        nullable: true
                        example: javascript
                      error_message:
                        type: string
                        description: Present only while the instance is failing
                      framework:
                        description: Design system version and asset URLs the markup
                          renders against
                      settings:
                        description: The instance settings, with sensitive values
                          removed
                      form_fields:
                        type: array
                        items:
                          description: A form field definition
                      custom_fields:
                        type: array
                        items:
                          description: A user-defined form field
                      available_fields:
                        description: Fields writable under the current settings
                      sizes:
                        type: object
                        additionalProperties:
                          type: boolean
                        description: Each markup size, and whether markup is attached
                          at it
        '404':
          description: UUID belonging to another user
  "/api/plugins":
    get:
      summary: List plugins available to install
      tags:
      - Plugins
      operationId: listPlugins
      description: The published catalog (native and third-party) plus your own third-party
        plugins at any status. Send a plugin id to createPluginSetting.
      security:
      - bearer_auth: []
      parameters:
      - name: category
        in: query
        required: false
        description: One of Plugin::CATEGORIES
        schema:
          type: string
      - name: query
        in: query
        required: false
        description: Words to find in the plugin name, description or categories
        schema:
          type: string
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
        description: Selects a cursor page with items and next_cursor instead of the
          default array
      - name: cursor
        in: query
        required: false
        schema:
          type: string
        description: Opaque cursor bound to this account and filters
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Plugin"
  "/api/plugins/{id}":
    get:
      summary: Read a plugin and its form fields
      tags:
      - Plugins
      operationId: getPlugin
      description: form_fields lists what to send updatePluginSettingFields, keyed
        by the same keyname.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Plugin id or keyname
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Plugin"
        '404':
          description: No plugin matches the id or keyname
  "/api/recipes":
    get:
      summary: Search published recipes
      tags:
      - Recipes
      operationId: searchRecipes
      description: 'Recipes are plugin settings other users published. Use one or
        two keywords, or #category to filter by category.'
      security:
      - bearer_auth: []
      parameters:
      - name: query
        in: query
        required: false
        description: 'Keywords, or #category'
        schema:
          type: string
      - name: sort_by
        in: query
        required: false
        enum:
        - newest
        - oldest
        - popularity
        - install
        - fork
        schema:
          type: string
        description: ":\n * `newest` \n * `oldest` \n * `popularity` \n * `install`
          \n * `fork` \n "
      - name: limit
        in: query
        required: false
        description: 1 to 25, default 10
        schema:
          type: integer
      - name: page_size
        in: query
        required: false
        schema:
          type: integer
          minimum: 1
          maximum: 100
        description: Selects a cursor page with items and next_cursor instead of the
          default array
      - name: cursor
        in: query
        required: false
        schema:
          type: string
        description: Opaque cursor bound to this account and filters
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Lists the newest recipes when no query is given
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/Recipe"
  "/api/recipes/{id}":
    get:
      summary: Read a recipe and how it installs
      tags:
      - Recipes
      operationId: getRecipe
      description: 'install_method says what installRecipe will do: simple_install
        mirrors the author''s render; read_only_fork copies the recipe so the installer
        fills its custom fields; oauth_choice is a fork that can also carry the author''s
        OAuth connection when inherit_oauth is true.'
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Recipe id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: A recipe with custom fields must be forked so the installer
            can fill them
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Recipe"
        '404':
          description: Not a published recipe
  "/api/recipes/{id}/markup":
    get:
      summary: Read the markup of a recipe
      tags:
      - Recipes
      operationId: getRecipeMarkup
      description: A starting point for a private plugin of your own; writeMarkup
        takes each size.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Recipe id
        schema:
          type: integer
      - name: sizes
        in: query
        required: false
        description: 'Comma-separated markup sizes to read, default all: markup_full,
          markup_half_horizontal, markup_half_vertical, markup_quadrant, shared_markup'
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      id:
                        type: integer
                      name:
                        type: string
                      strategy:
                        type: string
                        nullable: true
                      custom_fields:
                        type: array
                        items:
                          type: object
                      markups:
                        type: object
                        additionalProperties:
                          type: string
                        description: Markup by size
        '422':
          description: A size that does not exist
        '404':
          description: Not a published recipe
  "/api/recipes/{id}/installs":
    post:
      summary: Install a recipe
      tags:
      - Recipes
      operationId: installRecipe
      description: Adds the recipe to the account the way getRecipe's install_method
        says, and to the device playlist when a device_id is given.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Recipe id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Installs without a playlist when no device is given
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      plugin_setting:
                        "$ref": "#/components/schemas/PluginSetting"
                      install_method:
                        type: string
                        enum:
                        - simple_install
                        - read_only_fork
                        - oauth_choice
        '404':
          description: Not a published recipe
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                device_id:
                  type: integer
                  description: Device whose playlist the install joins
                inherit_oauth:
                  type: boolean
                  description: 'For an oauth_choice recipe: carry the author''s OAuth
                    connection'
  "/api/search_insights":
    get:
      summary: Read what users search for and find nothing
      tags:
      - Search insights
      operationId: getSearchInsights
      description: Search terms that found no plugin or recipe for at least 5 users
        in the last 30 days, most searched first. Demand is a band (5+, 10+, 15+,
        20+), never an exact count. Refreshed hourly.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        query:
                          type: string
                        demand:
                          type: string
  "/api/devices/{device_id}/timeline":
    get:
      summary: Read a device timeline
      tags:
      - Devices
      operationId: getDeviceTimeline
      description: 'One day of a device: the check-ins it made (recorded, from telemetry)
        and, on today, the check-ins it will make (expected, simulated from its playlist
        and settings). source narrows both halves to one plugin setting or mashup.'
      security:
      - bearer_auth: []
      parameters:
      - name: device_id
        in: path
        required: true
        description: Device id
        schema:
          type: integer
      - name: date
        in: query
        required: false
        description: ISO 8601 date in the owner’s time zone, default today; an unreadable
          date reads as today
        schema:
          type: string
      - name: source
        in: query
        required: false
        description: PluginSetting:<id> or Mashup:<id>, one of the account’s own
        schema:
          type: string
      - name: hours
        in: query
        required: false
        enum:
        - 3
        - 12
        - 24
        description: "How far ahead the strip looks, default 24:\n * `3` \n * `12`
          \n * `24` \n "
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Timeline"
        '404':
          description: Source belongs to another user
  "/api/plugin_settings/{plugin_setting_id}/timeline":
    get:
      summary: Read a plugin setting timeline
      tags:
      - Plugin Settings
      operationId: getPluginSettingTimeline
      description: 'One day of a plugin setting across every device that shows it:
        the check-ins that served it (recorded) and, on today, the ones that will
        (expected), each naming its device.'
      security:
      - bearer_auth: []
      parameters:
      - name: plugin_setting_id
        in: path
        required: true
        description: Plugin setting id
        schema:
          type: integer
      - name: date
        in: query
        required: false
        description: ISO 8601 date in the owner’s time zone, default today
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Timeline"
        '404':
          description: Plugin setting belongs to another user
  "/api/mashups/{mashup_id}/timeline":
    get:
      summary: Read a mashup timeline
      tags:
      - Mashups
      operationId: getMashupTimeline
      description: 'One day of a mashup across every device that shows it: the check-ins
        that served it (recorded) and, on today, the ones that will (expected), each
        naming its device.'
      security:
      - bearer_auth: []
      parameters:
      - name: mashup_id
        in: path
        required: true
        description: Mashup id
        schema:
          type: integer
      - name: date
        in: query
        required: false
        description: ISO 8601 date in the owner’s time zone, default today
        schema:
          type: string
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/Timeline"
        '404':
          description: Mashup belongs to another user
  "/api/user_themes":
    get:
      summary: List my themes
      tags:
      - User Themes
      operationId: listUserThemes
      description: Requires the theme builder to be enabled for the account.
      security:
      - bearer_auth: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/UserTheme"
        '404':
          description: The theme builder is not enabled for the account
    post:
      summary: Create a theme
      tags:
      - User Themes
      operationId: createUserTheme
      description: 'Requires the theme builder to be enabled for the account. settings
        is validated the same way the theme builder validates it: a value outside
        the vocabulary (an unrecognized token, surface, hue, or advanced channel)
        answers 422; a key the recipe does not read is ignored. advanced is either
        a mapping (channels/slots restated on one token) or the same mapping as YAML
        text.'
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/UserTheme"
        '422':
          description: Rejects advanced YAML that does not parse
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Unique per account
                settings:
                  "$ref": "#/components/schemas/UserThemeSettings"
              required:
              - name
        required: true
  "/api/user_themes/{id}":
    get:
      summary: Read a theme
      tags:
      - User Themes
      operationId: getUserTheme
      description: scss is the framework stylesheet this theme exports, the same file
        the builder downloads. Requires the theme builder to be enabled for the account.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Theme id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/UserTheme"
        '404':
          description: Theme belongs to another user
    patch:
      summary: Update a theme
      tags:
      - User Themes
      operationId: updateUserTheme
      description: Requires the theme builder to be enabled for the account.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Theme id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/UserTheme"
        '422':
          description: Rejects settings outside the recipe vocabulary
        '404':
          description: Theme belongs to another user
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                settings:
                  "$ref": "#/components/schemas/UserThemeSettings"
        required: true
    delete:
      summary: Delete a theme
      tags:
      - User Themes
      operationId: deleteUserTheme
      description: Requires the theme builder to be enabled for the account.
      security:
      - bearer_auth: []
      parameters:
      - name: id
        in: path
        required: true
        description: Theme id
        schema:
          type: integer
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '204':
          description: Deleted
        '404':
          description: Theme belongs to another user
  "/api/user_themes/imports":
    post:
      summary: Create a theme from a shared theme snippet
      tags:
      - User Themes
      operationId: importUserTheme
      description: Decodes a /t/:token share link (the "Copy share link" button on
        a theme) through the same settings parse createUserTheme uses, and saves it
        under name. Requires the theme builder to be enabled for the account.
      security:
      - bearer_auth: []
      parameters: []
      responses:
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '200':
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    "$ref": "#/components/schemas/UserTheme"
        '422':
          description: name is missing
        '404':
          description: The theme builder is not enabled for the account
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                token:
                  type: string
                  description: The token from a /t/:token share link, with or without
                    the URL around it
                name:
                  type: string
                  description: Unique per account
              required:
              - token
              - name
        required: true
servers:
- url: https://{defaultHost}
  variables:
    defaultHost:
      default: trmnl.com
components:
  securitySchemes:
    bearer_auth:
      type: http
      scheme: bearer
      bearerFormat: API key or OAuth access token
  schemas:
    PluginConnectionAttempt:
      type: object
      required:
      - id
      - state
      - expires_at
      - connection_id
      - target
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        state:
          type: string
          enum:
          - pending
          - exchanging
          - exchanged
          - connected
          - denied
          - failed
          - expired
          - cancelled
        expires_at:
          type: string
          format: date-time
        connection_id:
          type: string
        launch_url:
          type: string
          format: uri
        target:
          type: object
          required:
          - kind
          - id
          properties:
            kind:
              type: string
              enum:
              - instance
              - installation
            id:
              oneOf:
              - type: integer
              - type: string
                format: uuid
    CatalogEntry:
      type: object
      required:
      - kind
      - id
      - name
      - description
      - categories
      properties:
        kind:
          type: string
          enum:
          - official
          - recipe
        id:
          type: integer
        name:
          type: string
        description:
          type: string
          nullable: true
        categories:
          type: array
          items:
            type: string
        source_revision:
          type: string
        requirements:
          type: array
          items:
            type: string
        compatibility:
          type: object
        install_choices:
          type: array
          items:
            type: object
            required:
            - id
            - label
            - requirements
            properties:
              id:
                type: string
              label:
                type: string
              help:
                type: string
              requirements:
                type: array
                items:
                  type: string
    PluginInstallation:
      type: object
      required:
      - id
      - state
      - source
      - plugin_setting_id
      - expires_at
      properties:
        id:
          type: string
          format: uuid
        state:
          type: string
          enum:
          - setup_required
          - ready
          - completed
          - cancelled
          - expired
          - removed
        source:
          type: object
          required:
          - kind
          - id
          - revision
          - choice_id
          properties:
            kind:
              type: string
            id:
              type: integer
            revision:
              type: string
            choice_id:
              type: string
            device_id:
              type: integer
        plugin_setting_id:
          type: integer
          nullable: true
        expires_at:
          type: string
          format: date_time
    ConfigurationField:
      type: object
      required:
      - path
      - type
      - label
      - required
      - read_only
      - nullable
      - constraints
      properties:
        path:
          type: string
          pattern: "^/values/"
        type:
          type: string
        label:
          type: string
        help:
          type: string
        required:
          type: boolean
        read_only:
          type: boolean
        nullable:
          type: boolean
        constraints:
          type: object
        choices:
          oneOf:
          - type: array
            items:
              type: object
              required:
              - label
              - value
              properties:
                label:
                  type: string
                value: {}
          - type: object
            required:
            - resolver_id
            - depends_on
            - searchable
            - paginated
            properties:
              resolver_id:
                type: string
              depends_on:
                type: array
                items:
                  type: string
              searchable:
                type: boolean
              paginated:
                type: boolean
    PluginSync:
      type: object
      required:
      - source_kinds
      - upload_allowed
      properties:
        source_kinds:
          type: array
          items:
            type: string
        upload_allowed:
          type: boolean
        reason:
          type: string
          nullable: true
        action_id:
          type: string
          nullable: true
    PluginConfiguration:
      type: object
      required:
      - schema_version
      - revision
      - required_capabilities
      - sections
      - values
      - secrets
      - connections
      - actions
      - readiness
      - sync
      properties:
        schema_version:
          type: integer
          enum:
          - 1
        revision:
          type: string
        required_capabilities:
          type: array
          items:
            type: string
        sections:
          type: array
          items:
            type: object
            required:
            - id
            - title
            - fields
            properties:
              id:
                type: string
              title:
                type: string
              fields:
                type: array
                items:
                  "$ref": "#/components/schemas/ConfigurationField"
        values:
          type: object
          description: Only declared nonsecret values. Secret values are never returned.
        secrets:
          type: object
          additionalProperties:
            type: object
            required:
            - present
            properties:
              present:
                type: boolean
        connections:
          type: array
          items:
            type: object
        actions:
          type: array
          items:
            type: object
        readiness:
          type: object
          required:
          - ready
          - blocking_paths
          properties:
            ready:
              type: boolean
            blocking_paths:
              type: array
              items:
                type: string
            reason:
              type: string
              nullable: true
        sync:
          "$ref": "#/components/schemas/PluginSync"
    ConfigurationChange:
      type: object
      required:
      - op
      - path
      additionalProperties: false
      properties:
        op:
          type: string
          enum:
          - set
          - unset
          - clear_secret
        path:
          type: string
          pattern: "^/values/"
        value: {}
    ConfigurationWrite:
      type: object
      required:
      - revision
      - changes
      properties:
        revision:
          type: string
        changes:
          type: array
          maxItems: 200
          items:
            "$ref": "#/components/schemas/ConfigurationChange"
    ConfigurationError:
      type: object
      required:
      - error
      properties:
        error:
          type: object
          required:
          - code
          - message
          - field_errors
          properties:
            code:
              type: string
            message:
              type: string
            field_errors:
              type: array
              items:
                type: object
                required:
                - path
                - code
                - message
                properties:
                  path:
                    type: string
                  code:
                    type: string
                  message:
                    type: string
    MashupOptions:
      type: object
      required:
      - layouts
      - plugins
      properties:
        layouts:
          type: array
          items:
            type: object
            required:
            - id
            - name
            - columns
            - rows
            - sections
            properties:
              id:
                type: string
              name:
                type: string
              columns:
                type: integer
              rows:
                type: integer
              sections:
                type: array
                items:
                  type: object
                  required:
                  - position
                  - column
                  - row
                  - column_span
                  - row_span
                  properties:
                    position:
                      type: string
                    column:
                      type: integer
                    row:
                      type: integer
                    column_span:
                      type: integer
                    row_span:
                      type: integer
        plugins:
          type: array
          items:
            type: object
            required:
            - id
            - name
            - plugin_name
            - compatible_layouts
            properties:
              id:
                type: integer
              name:
                type: string
              plugin_name:
                type: string
              compatible_layouts:
                type: array
                items:
                  type: string
    Error:
      type: object
      additionalProperties: false
      properties:
        error:
          type: string
          example: An error occurred
    Device:
      type: object
      additionalProperties: false
      properties:
        management:
          type: object
        id:
          type: integer
          example: 123
        name:
          type: string
          example: My TRMNL
        friendly_id:
          type: string
          example: ABC-123
        mac_address:
          type: string
          example: "••:••:••:••:9A:BC"
          description: All but the last two bytes are masked for an API key or an
            agent; the account API key reads it whole.
        firmware_version:
          type: string
          nullable: true
          example: 1.8.14
          description: Version the device reported at its last check-in
        firmware_channel:
          type: string
          example: production
          description: Release channel the device follows
        ota_enabled:
          type: boolean
          example: true
          description: Whether the device accepts over-the-air updates
        pinned_firmware_version:
          type: string
          nullable: true
          example: 1.8.14
          description: Version the device is pinned to, if any
        battery_voltage:
          type: number
          nullable: true
          example: 3.7
        rssi:
          type: integer
          nullable: true
          example: -70
        wifi_band:
          type: string
          nullable: true
          example: '5'
          description: WiFi band the device is connected on ("2.4" or "5")
        refresh_interval:
          type: integer
          example: 900
          description: Seconds between check-ins, 300 to 86400
        orientation:
          type: integer
          example: 0
        sleep_screen_enabled:
          type: boolean
          example: true
        low_battery_notification_enabled:
          type: boolean
          example: true
        auto_advance:
          type: boolean
          description: 'Beta: may change or go away. Present only when management.writable_fields
            lists it'
        sleep_mode_enabled:
          type: boolean
          nullable: false
          example: false
        sleep_start_time:
          type: integer
          example: 1320
        sleep_end_time:
          type: integer
          example: 480
        sleep_until:
          type: string
          format: date_time
          nullable: true
          example: '2026-10-01T15:00:00.000Z'
          description: A one-time sleep the device takes at its next check-in, then
            clears
        last_ping_at:
          type: string
          format: date_time
          nullable: true
          example: '2026-03-31T14:30:00.000Z'
        hardware_last_ping_at:
          type: string
          format: date_time
          nullable: true
          example: '2026-03-31T14:30:00.000Z'
        percent_charged:
          type: number
          example: 85.0
          minimum: 0
          maximum: 100
        wifi_strength:
          type: number
          example: 75.0
          minimum: 0
          maximum: 100
        mashup_layouts:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          example:
            1Lx1R:
            - a
            - b
            2x2:
            - a
            - b
            - c
            - d
          description: The mashup layouts the device can show and the positions to
            fill in each; 3x3 lists none because its grid_config names them
    Model:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          example: trmnl_original
          description: Unique identifier
        label:
          type: string
          example: TRMNL
          description: Human-readable name
        description:
          type: string
          example: Original TRMNL model
          description: Description
        width:
          type: integer
          example: 800
          description: Screen width in pixels
        height:
          type: integer
          example: 480
          description: Screen height in pixels
        colors:
          type: integer
          example: 2
          description: Number of colors supported
        bit_depth:
          type: integer
          example: 1
          description: Color bit depth
        scale_factor:
          type: number
          example: 1.0
          description: Display scale factor
        rotation:
          type: integer
          example: 90
          description: Screen rotation in degrees
        mime_type:
          type: string
          example: image/png
          description: Image MIME type
        offset_x:
          type: integer
          example: 10
          description: X offset for image rendering
        offset_y:
          type: integer
          example: 20
          description: Y offset for image rendering
        kind:
          type: string
          example: trmnl
          description: Device kind (e.g., trmnl, kindle, byod)
          enum:
          - trmnl
          - kindle
          - byod
          - tidbyt
        palette_ids:
          type: array
          items:
            type: string
          example:
          - bw
          - gray-4
          - gray-16
          description: Supported color palette IDs
        preview_white_point:
          type: string
          example: true_white
          description: Preview white point mode for color previews
          enum:
          - true_white
          - limited
        image_size_limit:
          type: integer
          nullable: true
          example: 90000
          description: Maximum image file size in bytes for webhook uploads; null
            means the model declares no limit
        image_upload_supported:
          type: boolean
          example: true
          description: Whether webhook image uploads are supported for this device
            type
        css:
          type: object
          nullable: true
          description: CSS classes and variables for web rendering
          properties:
            classes:
              type: object
              properties:
                device:
                  type: string
                  example: screen--og_plus
                size:
                  type: string
                  example: screen--md
                density:
                  type: string
                  example: screen--density-1x
            variables:
              type: array
              items:
                type: array
                items:
                  type: string
                example:
                - "--screen-w"
                - 800px
    Palette:
      type: object
      additionalProperties: false
      required:
      - id
      - name
      properties:
        id:
          type: string
          example: gray-16
          description: Unique identifier
        name:
          type: string
          example: 16-Gray
          description: Human-readable name
        grays:
          type: integer
          nullable: true
          example: 16
          description: Number of grayscale levels (null for color palettes)
        colors:
          type: array
          nullable: true
          items:
            type: string
          example:
          - "#FF0000"
          - "#00FF00"
          - "#0000FF"
          - "#FFFF00"
          - "#000000"
          - "#FFFFFF"
          description: Array of hex color codes (null for grayscale palettes)
        framework_class:
          type: string
          example: screen--4bit
          description: Framework CSS class for this palette
        grayscale_bit_depth:
          type: integer
          nullable: true
          example: 1
          description: For color palettes, bit depth for grayscale areas (1 for 3/4-color
            limited, 2 for 6/7-color, 4 for full-spectrum)
    PlaylistItem:
      type: object
      additionalProperties: false
      properties:
        presentation:
          type: object
        created_at:
          type: string
          format: date_time
          example: '2023-10-01T12:00:00Z'
        configuration_state:
          type: string
          example: configured
          enum:
          - configured
          - needs_configuration
        device_id:
          type: integer
          example: 1
        id:
          type: integer
          example: 1
        mashup_id:
          type: integer
          example: 1
          nullable: true
        mirror:
          type: boolean
          example: true
        plugin:
          type: object
          nullable: true
          properties:
            id:
              type: integer
              example: 1
            name:
              type: string
              example: Weather
            keyname:
              type: string
              example: weather
            description:
              type: string
              nullable: true
            image:
              type: string
              nullable: true
            image_dark:
              type: string
              nullable: true
        plugin_id:
          type: integer
          example: 1
          nullable: true
        plugin_setting:
          "$ref": "#/components/schemas/PluginSetting"
        plugin_setting_id:
          type: integer
          example: 1
          nullable: true
        rendered_at:
          type: string
          format: date_time
          example: '2023-10-01T12:00:00Z'
          nullable: true
        row_order:
          type: integer
          example: 1
        updated_at:
          type: string
          format: date_time
          example: '2023-10-01T12:00:00Z'
        visible:
          type: boolean
          example: true
        palette_id:
          type: string
          nullable: true
          example: bw
          description: Overrides the device palette for this item
        font_family:
          type: string
          nullable: true
          example: classic
        text_scale:
          type: string
          nullable: true
          example: large
        theme:
          type: string
          nullable: true
          example: dark
    Mashup:
      type: object
      additionalProperties: false
      properties:
        version:
          type: string
        id:
          type: integer
          example: 1
        layout:
          type: string
          example: 1Lx1R
          enum:
          - 1x1
          - 1Tx1B
          - 1Lx1R
          - 1Tx2B
          - 2Tx1B
          - 1Lx2R
          - 2Lx1R
          - 2x2
          - 3x3
        grid_config:
          type: array
          nullable: true
          description: The cells of a 3x3 layout
          items:
            type: object
            properties:
              pos:
                type: string
              col:
                type: integer
              row:
                type: integer
              cs:
                type: integer
              rs:
                type: integer
        positions:
          type: array
          items:
            type: string
          example:
          - a
          - b
          description: The sections the layout has
        contents:
          type: object
          additionalProperties:
            type: integer
          example:
            a: 1
            b: 2
          description: Plugin setting id by position
        health_notification_enabled:
          type: boolean
          example: true
        created_at:
          type: string
          format: date_time
          example: '2023-10-01T12:00:00Z'
        updated_at:
          type: string
          format: date_time
          example: '2023-10-01T12:00:00Z'
    Recipe:
      type: object
      additionalProperties: false
      properties:
        kind:
          type: string
          enum:
          - official
          - recipe
        source_revision:
          type: string
        requirements:
          type: array
          items:
            type: string
        compatibility:
          type: object
        install_choices:
          type: array
          items:
            type: object
        id:
          type: integer
          example: 1
        name:
          type: string
          example: Train Departures
        description:
          type: string
          nullable: true
        author:
          type: string
          nullable: true
          example: Ada
        categories:
          type: array
          nullable: true
          items:
            type: string
        ai_tags:
          type: array
          nullable: true
          items:
            type: string
        ai_keywords:
          type: array
          nullable: true
          items:
            type: string
        stats:
          type: object
          properties:
            installs:
              type: integer
            forks:
              type: integer
        strategy:
          type: string
          nullable: true
          example: polling
        custom_fields:
          type: array
          items:
            type: object
          description: Fields a fork needs filled
        published_at:
          type: string
          format: date_time
          nullable: true
        screenshot_url:
          type: string
          nullable: true
        install_method:
          type: string
          enum:
          - simple_install
          - read_only_fork
          - oauth_choice
          description: getRecipe only
        share_oauth_config:
          type: boolean
          description: getRecipe only
    Screen:
      type: object
      additionalProperties: false
      properties:
        image_url:
          type: string
          example: https://trmnl-screens.s3.amazonaws.com/...
          description: Presigned; expires after ActiveStorage.service_urls_expire_in
            (5 minutes by default)
        rendered_at:
          type: string
          format: date_time
          nullable: true
          example: '2023-10-01T12:00:00Z'
        playlist_item_id:
          type: integer
          example: 1
        plugin_setting_id:
          type: integer
          nullable: true
          example: 1
        mashup_id:
          type: integer
          nullable: true
          example: 1
        filename:
          type: string
          example: weather-1696161600
    PlaylistItemParams:
      type: object
      additionalProperties: false
      properties:
        visible:
          type: boolean
          example: true
    Plugin:
      type: object
      additionalProperties: false
      properties:
        kind:
          type: string
          enum:
          - official
          - recipe
        source_revision:
          type: string
        requirements:
          type: array
          items:
            type: string
        compatibility:
          type: object
        install_choices:
          type: array
          items:
            type: object
        categories:
          type: array
          nullable: true
          items:
            type: string
        id:
          type: integer
          example: 1
        keyname:
          type: string
          example: weather
          description: The plugin_id createPluginSetting expects, as a string
        name:
          type: string
          example: Weather
        description:
          type: string
          nullable: true
          example: Current conditions and forecast
        category:
          type: array
          nullable: true
          items:
            type: string
          example:
          - news
        plugin_type:
          type: string
          example: native
          enum:
          - native
          - third_party
        form_type:
          type: string
          example: form_input
          enum:
          - form_input
          - oauth2
        oauth:
          type: boolean
          example: false
          description: Whether connecting this plugin needs an OAuth authorize step
        image_url:
          type: string
          nullable: true
          example: https://trmnl.com/images/plugins/weather.svg
        refresh_every:
          type: integer
          nullable: true
          example: 30
          description: Minutes between polls
        installable:
          type: boolean
          example: true
          description: False for an oauth2 plugin whose authorize step needs a browser
        form_fields:
          type: array
          nullable: true
          description: getPlugin only — send these keynames to updatePluginSettingFields
          items:
            type: object
            properties:
              keyname:
                type: string
                example: username
              field_type:
                type: string
                example: string
              name:
                type: string
                example: User Name
              description:
                type: string
                nullable: true
              help_text:
                type: string
                nullable: true
              placeholder:
                type: string
                nullable: true
              optional:
                type: boolean
                nullable: true
              options:
                nullable: true
                description: An array of choices, or a string naming a dynamic source
              default:
                nullable: true
    PluginSetting:
      type: object
      additionalProperties: false
      properties:
        sync:
          "$ref": "#/components/schemas/PluginSync"
        id:
          type: integer
          example: 1
        name:
          type: string
          example: My Plugin Setting
        description:
          type: string
          example: Upcoming train departures
          nullable: true
        plugin_id:
          type: integer
          example: 1
        refresh_interval:
          type: integer
          example: 60
          nullable: true
          description: Minutes between renders
        health_notification_enabled:
          type: boolean
          example: false
        icon_url:
          type: string
          example: https://trmnl-public.s3.com/qwertyasdf
          nullable: true
        icon_content_type:
          type: string
          example: image/png
          nullable: true
        read_only?:
          type: boolean
          example: false
        strategy:
          type: string
          example: webhook
          nullable: true
    PluginSettingArchive:
      type: object
      properties:
        settings_yaml:
          type: string
          description: YAML settings file
    PluginSettingParams:
      type: object
      additionalProperties: false
      required:
      - name
      - plugin_id
      properties:
        name:
          type: string
          example: My Plugin Setting
        plugin_id:
          type: integer
          example: 1
    PluginSettingDataParams:
      type: object
      additionalProperties: false
      required:
      - merge_variables
      properties:
        merge_variables:
          description: The rejected value, echoed back as sent
        error:
          type: string
    UserTheme:
      type: object
      additionalProperties: false
      properties:
        id:
          type: integer
          example: 1
        name:
          type: string
          example: Sunny Days
        settings:
          "$ref": "#/components/schemas/UserThemeSettings"
        created_at:
          type: string
          format: date_time
          example: '2023-10-01T12:00:00Z'
        updated_at:
          type: string
          format: date_time
          example: '2023-10-01T12:00:00Z'
        scss:
          type: string
          description: 'getUserTheme only: the framework SCSS file this theme exports'
    UserThemeSettings:
      type: object
      additionalProperties: false
      description: Every key is optional; an omitted key keeps the framework default.
        A value outside the vocabulary (an unrecognized token, surface, hue, or advanced
        channel) answers 422; a key the recipe does not read is ignored, not rejected.
      properties:
        font:
          type: string
          nullable: true
          description: A FontPack id such as "bitter" or "space-mono", or blank for
            the device font
        chip:
          type: string
          enum:
          - filled
          - outlined
        screen_scale:
          type: string
          description: '"none", "invert", or a hue side like "red-dark"/"red-bright"'
        surfaces:
          type: object
          additionalProperties:
            type: string
          description: paper, text, muted_text, strong_fill, soft_fills, borders,
            title_bar, progress, dividers — each a color token ("black", "white",
            "gray-30", "red-40", ...) or "hidden" for dividers
        remap:
          type: object
          properties:
            grays:
              type: string
              description: '"keep", "invert", a hue side, "<hue>-linear", or any color
                token'
            colors:
              type: object
              additionalProperties:
                type: string
              description: 'target token per hue: red, orange, yellow, lime, green,
                cyan, blue, violet, purple, pink'
        layout:
          type: object
          properties:
            whitespace:
              type: string
              enum:
              - compact
              - regular
              - airy
              - spacious
            corners:
              type: string
              enum:
              - sharp
              - regular
              - round
            title_bar_height:
              type: string
              enum:
              - slim
              - regular
              - tall
            progress:
              type: string
              enum:
              - slim
              - regular
              - thick
        typography:
          type: object
          additionalProperties:
            type: string
          description: weight ("light"/"regular"/"medium"/"bold"), plus <role>_case
            ("as-written"/"uppercase"/"lowercase") and <role>_tracking ("normal"/"wide"/"wider")
            for title, value, label, description, title_bar, title_bar_instance, table_head
            (table_body has no _case)
        spacing:
          type: object
          properties:
            title_bar_padding:
              type: string
              enum:
              - flush
              - regular
              - wide
            item_padding:
              type: string
              enum:
              - none
              - small
              - regular
              - large
        title_bar:
          type: object
          description: ink, instance, stroke, instance_stroke — a color token, or
            "auto"/"inherit"
          additionalProperties:
            type: string
        item:
          type: object
          properties:
            fill:
              type: string
              description: '"none", "ramp", or a color token'
            border:
              type: string
              enum:
              - none
              - outline
              - corner-brackets
            ink:
              type: string
              description: '"auto" or a color token'
        advanced:
          type: object
          additionalProperties: true
          description: 'Framework contract channels and slots restated on one token,
            e.g. { "channels": { "canvas": "black" } }. Also accepted as the same
            mapping written as YAML text.'
    MyPluginParams:
      type: object
      additionalProperties: false
      properties:
        name:
          type: string
          example: Metro Schedule
        description:
          type: string
          example: Local train and bus times
          maxLength: 35
        category:
          type: array
          items:
            type: string
            enum:
            - album
            - analytics
            - art
            - calendar
            - comics
            - crm
            - custom
            - discovery
            - ecommerce
            - education
            - email
            - entertainment
            - environment
            - finance
            - games
            - humor
            - images
            - kpi
            - life
            - marketing
            - morbid
            - nature
            - news
            - personal
            - productivity
            - programming
            - sales
            - sports
            - travel
          description: One or more of the categories listCategories answers
        refresh_every:
          type: integer
          enum:
          - 1440
          - 720
          - 480
          - 360
          - 240
          - 120
          - 60
          - 30
          - 15
          description: Minutes between renders
        no_screen_padding:
          type: boolean
        installation_url:
          type: string
          description: Where an installer is sent to connect; your OAuth authorize
            page
        plugin_management_url:
          type: string
          nullable: true
          description: Where an installer manages the connection on your site
        plugin_markup_url:
          type: string
          description: Your endpoint that answers the markup for a render
        installation_success_webhook_url:
          type: string
          nullable: true
        uninstallation_webhook_url:
          type: string
          nullable: true
        knowledge_base_url:
          type: string
          nullable: true
    MyPlugin:
      type: object
      additionalProperties: false
      properties:
        id:
          type: integer
        name:
          type: string
          example: Metro Schedule
        keyname:
          type: string
          example: metro_schedule
        description:
          type: string
          nullable: true
        plugin_type:
          type: string
          enum:
          - third_party
        status:
          type: string
          enum:
          - development
          - in_review
          - published
        category:
          type: array
          items:
            type: string
        refresh_every:
          type: integer
          description: Minutes between renders
        no_screen_padding:
          type: boolean
        installation_url:
          type: string
          nullable: true
        plugin_management_url:
          type: string
          nullable: true
        plugin_markup_url:
          type: string
          nullable: true
        installation_success_webhook_url:
          type: string
          nullable: true
        uninstallation_webhook_url:
          type: string
          nullable: true
        knowledge_base_url:
          type: string
          nullable: true
        client_id:
          type: string
          description: The plugin client id; its secret is on the dashboard only
        created_at:
          type: string
          format: date_time
        updated_at:
          type: string
          format: date_time
    User:
      type: object
      additionalProperties: false
      properties:
        id:
          type: integer
          example: 42
        name:
          type: string
          example: Jim Bob
        email:
          type: string
          example: jimbob@gmail.net
        first_name:
          type: string
          example: Jim
        last_name:
          type: string
          example: Bob
        locale:
          type: string
          example: en
        time_zone:
          type: string
          example: Eastern Time (US & Canada)
        time_zone_iana:
          type: string
          example: America/New_York
        utc_offset:
          type: integer
          example: -14400
        title_bar_enabled:
          type: boolean
          example: true
        account_name:
          type: string
          nullable: true
        low_battery_notification_email:
          type: string
          nullable: true
        api_key:
          type: string
          example: user_xxxxxx
          description: Only when the request authenticated with the account API key
    TimelineStep:
      type: object
      description: 'One simulated check-in: what the device fetches then, and how
        long it holds it'
      properties:
        at:
          type: string
          format: date_time
        device_id:
          type: integer
        playlist_item_id:
          type: integer
          nullable: true
          description: Null while asleep or with nothing to show
        source_type:
          type: string
          nullable: true
          enum:
          - PluginSetting
          - Mashup
          - Plugin
        source_id:
          type: integer
          nullable: true
        name:
          type: string
          nullable: true
          description: The plugin setting or mashup shown
        reason:
          type: string
          nullable: true
          description: 'Why the wait is what it is: asleep, or the schedule or render
            that moved it'
        refresh_seconds:
          type: integer
          description: How long the device waits before its next check-in
        refresh_rate_seconds:
          type: integer
          description: The wait before the next-render extension
        render_at:
          type: string
          format: date_time
          nullable: true
          description: When the shown content renders next
        render_reason:
          type: string
          nullable: true
          description: Why no render is scheduled
        waits_past_refresh_rate:
          type: boolean
          description: The device is held past its refresh rate by the "waits for
            next render" setting
    Timeline:
      type: object
      properties:
        date:
          type: string
          format: date
          description: The day shown, in the owner’s time zone
        today:
          type: string
          format: date
          description: Today in the owner’s time zone; expected and strip are empty
            on any other day
        zone:
          type: string
          example: America/New_York
        available:
          type: boolean
          description: Telemetry is configured; recorded is empty when it is not
        query_failed:
          type: boolean
          description: Telemetry could not be read for this request; recorded is empty
        truncated:
          type: boolean
          description: The day had more events than one page holds (1000)
        check_in:
          type: object
          nullable: true
          description: Whether the device checked in when it said it would; null for
            a plugin setting or mashup
          properties:
            state:
              type: string
              enum:
              - never_seen
              - overdue
              - asleep
              - on_time
            expected_at:
              type: string
              format: date_time
              nullable: true
        recorded:
          type: array
          description: The day’s check-ins and what they caused, oldest first, one
            entry per check-in
          items:
            type: object
            properties:
              at:
                type: string
                format: date_time
              name:
                type: string
                nullable: true
                description: The source shown on a device timeline; the device on
                  a plugin setting or mashup timeline
              outcome:
                type: string
                nullable: true
                description: What the check-in decided
              leading:
                type: object
                description: 'The event that names the entry: serve, render, checkin
                  or schedule; ts and refresh_at_after in the owner’s time zone, ids
                  as integers'
              supporting:
                type: array
                items:
                  type: object
                description: The other events of the same check-in, in the same shape
                  as leading
              trace_entries:
                type: array
                items:
                  type: object
                description: The decisions the check-in recorded
        expected:
          type: array
          items:
            "$ref": "#/components/schemas/TimelineStep"
          description: The coming check-ins over the next 24 hours; across every device
            for a plugin setting or mashup
        strip:
          type: array
          items:
            "$ref": "#/components/schemas/TimelineStep"
          description: A device’s whole rotation over the next hours, unfiltered by
            source; empty for a plugin setting or mashup
    App:
      type: object
      additionalProperties: false
      properties:
        app_key:
          type: string
          example: room_booking
        name:
          type: string
          example: Booking
        tagline:
          type: string
          example: Rooms and desks on your screens
        installed:
          type: boolean
          example: false
    AppInstallation:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
          example: 5e2d0a3c-8f1b-4c8a-9c1e-2f6a7b8c9d0e
        app_key:
          type: string
          example: fleet
        name:
          type: string
          example: Fleet
        summary:
          type: string
          nullable: true
          example: 3 mirrors
        created_at:
          type: string
          format: date_time
          example: '2026-09-22T12:00:00Z'
    FleetMember:
      type: object
      additionalProperties: false
      properties:
        device_id:
          type: integer
          example: 123
        name:
          type: string
          nullable: true
          example: Lobby
        role:
          type: string
          enum:
          - master
          - mirror
        check_in_state:
          type: string
          nullable: true
          enum:
          - overdue
          - on_time
          - asleep
          - never_seen
          description: Whether the device checked in when it said it would; null for
            a device that was reset
        expected_check_in_at:
          type: string
          format: date_time
          nullable: true
        last_pushed_at:
          type: string
          format: date_time
          nullable: true
        in_sync:
          type: boolean
          description: Pushed since the master playlist last changed
    Fleet:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          example: Fleet
        master:
          nullable: true
          allOf:
          - "$ref": "#/components/schemas/FleetMember"
        mirror_count:
          type: integer
          example: 3
        needs_push_count:
          type: integer
          example: 1
          description: Mirrors not pushed since the master playlist last changed
        overdue_count:
          type: integer
          example: 0
        last_pushed_at:
          type: string
          format: date_time
          nullable: true
        inherited_settings:
          type: array
          items:
            type: string
            enum:
            - refresh_interval
            - orientation
            - sleep
            - palette
          description: Master settings each push copies to the mirrors
        alerts:
          type: object
          properties:
            enabled:
              type: boolean
            threshold_minutes:
              type: integer
              example: 60
            email:
              type: string
              nullable: true
    RoomBookingCalendar:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          example: ical:12
          description: '"<kind>:<number>", the id every calendar operation takes'
        kind:
          type: string
          enum:
          - ical
          - google
          - microsoft
        name:
          type: string
          example: Boardroom
        timezone:
          type: string
          example: Europe/Amsterdam
        capacity:
          type: integer
          nullable: true
        show_booking_qr:
          type: boolean
        show_company_logo:
          type: boolean
        feed_backed:
          type: boolean
          description: 'Read-only feed: bookings are made in the source calendar,
            not here'
        ics_url:
          type: string
          nullable: true
          description: iCal calendars only
        resource_kind:
          type: string
          nullable: true
          example: room
          description: Google and Microsoft calendars only
        booking_mode:
          type: string
          nullable: true
          enum:
          -
          - reservable
          - on_demand
          - unavailable
          description: Google and Microsoft calendars only
        last_synced_at:
          type: string
          format: date_time
          nullable: true
        last_sync_error:
          type: string
          nullable: true
        bound_device_ids:
          type: array
          items:
            type: integer
          description: Devices showing this calendar
        url_display_url:
          type: string
          nullable: true
          description: Browser display URL, or null when no URL display is configured
        public_booking_token:
          type: string
          nullable: true
          description: Public booking token, when Booking with TRMNL is open
        public_booking_url:
          type: string
          nullable: true
    RoomBooking:
      type: object
      additionalProperties: false
      properties:
        id:
          type: integer
        title:
          type: string
          nullable: true
        starts_at:
          type: string
          format: date_time
        ends_at:
          type: string
          format: date_time
        all_day:
          type: boolean
        source:
          type: string
          enum:
          - owner
          - public_link
          - external
        status:
          type: string
          enum:
          - confirmed
          - cancelled
        booked_with_trmnl:
          type: boolean
          description: Made through TRMNL, so it can be canceled or ended here
    RoomBookingCollection:
      type: object
      additionalProperties: false
      properties:
        id:
          type: integer
        name:
          type: string
          example: First Floor
        collection_type:
          type: string
          enum:
          - any
          - rooms
          - spaces
          - desks
          - desk_pools
          - parking_spaces
          - vehicles
          - equipment
          - people
          - other
        display_layout:
          type: string
          enum:
          - list
          - grid
        show_booking_qr:
          type: boolean
        show_company_logo:
          type: boolean
        resource_ids:
          type: array
          items:
            type: string
          example:
          - ical:12
          - google:3
          description: The calendars in it
        bound_device_ids:
          type: array
          items:
            type: integer
          description: Devices showing this collection
        url_display_url:
          type: string
          nullable: true
        public_booking_token:
          type: string
          nullable: true
        public_booking_url:
          type: string
          nullable: true
    RoomBookingSummary:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          example: Booking
        timezone:
          type: string
          nullable: true
        configured:
          type: boolean
          description: An account is connected or a calendar exists
        connected_accounts:
          type: object
          properties:
            google_user:
              type: integer
            google_workspace:
              type: integer
            microsoft_user:
              type: integer
            microsoft_workspace:
              type: integer
        calendar_count:
          type: integer
        collection_count:
          type: integer
        sync_error_count:
          type: integer
          description: Calendars whose last fetch failed
        device_ids:
          type: array
          items:
            type: integer
          description: Devices showing a calendar or collection
        billing:
          type: object
          properties:
            locked:
              type: boolean
              description: Changes answer 402 until the subscription is settled
            subscription_required:
              type: boolean
              description: Rooms reach a screen but nothing is subscribed yet
            subscription_status:
              type: string
              nullable: true
              example: active
            billing_url:
              type: string
              description: Where the owner subscribes or manages the subscription,
                in the browser
    RoomBookingSettings:
      type: object
      additionalProperties: false
      properties:
        timezone:
          type: string
          nullable: true
          example: Europe/Amsterdam
        public_bookings:
          type: boolean
          description: Booking with TRMNL
        dark_mode:
          type: boolean
        show_company_logo:
          type: boolean
        daily_public_booking_url_rotation:
          type: boolean
        time_format:
          type: string
          nullable: true
          enum:
          -
          - 24_hour
          - 12_hour
          description: null detects it from the time zone
        walk_up_calendar:
          type: boolean
        walk_up_booking_window_days:
          type: integer
          enum:
          - 1
          - 2
          - 7
          - 30
        company_logo_attached:
          type: boolean
        color_company_logo_attached:
          type: boolean
    RoomBookingIntegration:
      type: object
      additionalProperties: false
      properties:
        id:
          type: integer
        integration_type:
          type: string
          enum:
          - google_user
          - google_workspace
          - microsoft_user
          - microsoft_workspace
        provider:
          type: string
          enum:
          - google
          - microsoft
        workspace:
          type: boolean
          description: An admin connection to a whole workspace, not one person's
            account
        label:
          type: string
          nullable: true
          example: owner@gmail.com
          description: The account email, or the workspace domain
        admin_email:
          type: string
          nullable: true
          description: Workspaces only
        booking_organizer_email:
          type: string
          nullable: true
          description: 'Microsoft workspaces only: the mailbox bookings are made from'
        parking_resource_group_id:
          type: string
          nullable: true
          description: Microsoft workspaces only
        calendar_count:
          type: integer
        selected_calendar_count:
          type: integer
          description: Calendars selected for sync, the ones listRoomBookingCalendars
            shows
        last_synced_at:
          type: string
          format: date_time
          nullable: true
        last_attempted_at:
          type: string
          format: date_time
          nullable: true
    PublicBookingEvent:
      type: object
      additionalProperties: false
      description: 'The shape the public booking page uses: times are local to the
        room, without a zone'
      properties:
        id:
          type: integer
        title:
          type: string
        startsAt:
          type: string
          example: 2026-06-12T10:00
        endsAt:
          type: string
          example: 2026-06-12T11:00
        actionLabel:
          type: string
          description: Present when the visitor may cancel or end it
        actionMethod:
          type: string
          enum:
          - delete
          - patch
        actionPath:
          type: string
          example: "/api/book/{token}/bookings/12"
        actionConfirm:
          type: string
    RoomBookingBilling:
      type: object
      additionalProperties: false
      properties:
        locked:
          type: boolean
          description: Changes answer 402 until the subscription is settled
        subscription_required:
          type: boolean
          description: Calendars reach a screen but nothing is subscribed yet
        billable_resource_count:
          type: integer
          description: Calendars on a screen, directly or through a collection
        amount_in_dollars:
          type: object
          properties:
            month:
              type: integer
              example: 8
            year:
              type: integer
              example: 80
          description: Per calendar
        trial_days:
          type: integer
          example: 14
        grace_until:
          type: string
          format: date_time
          nullable: true
        subscription:
          type: object
          nullable: true
          properties:
            id:
              type: string
            status:
              type: string
              example: active
            interval:
              type: string
              enum:
              - month
              - year
            billed_quantity:
              type: integer
        checkout_url:
          type: string
          description: The billing page, where the owner subscribes in the browser
        portal_url:
          type: string
          nullable: true
          description: The billing page, where a subscribed owner opens the Stripe
            portal
    RoomBookingDeviceScreen:
      type: object
      additionalProperties: false
      description: A device showing a calendar or a collection, and the image it is
        showing
      properties:
        device_id:
          type: integer
        device_name:
          type: string
          example: Boardroom display
        screen:
          nullable: true
          allOf:
          - "$ref": "#/components/schemas/Screen"
          description: null while that device has not rendered this calendar or collection
            yet
    RoomBookingCalendarDetails:
      type: object
      additionalProperties: false
      properties:
        id:
          type: string
          example: ical:12
          description: '"<kind>:<number>", the id every calendar operation takes'
        kind:
          type: string
          enum:
          - ical
          - google
          - microsoft
        name:
          type: string
          example: Boardroom
        timezone:
          type: string
          example: Europe/Amsterdam
        capacity:
          type: integer
          nullable: true
        show_booking_qr:
          type: boolean
        show_company_logo:
          type: boolean
        feed_backed:
          type: boolean
          description: 'Read-only feed: bookings are made in the source calendar,
            not here'
        ics_url:
          type: string
          nullable: true
          description: iCal calendars only
        resource_kind:
          type: string
          nullable: true
          example: room
          description: Google and Microsoft calendars only
        booking_mode:
          type: string
          nullable: true
          enum:
          -
          - reservable
          - on_demand
          - unavailable
          description: Google and Microsoft calendars only
        last_synced_at:
          type: string
          format: date_time
          nullable: true
        last_sync_error:
          type: string
          nullable: true
        bound_device_ids:
          type: array
          items:
            type: integer
          description: Devices showing this calendar
        url_display_url:
          type: string
          nullable: true
          description: Browser display URL, or null when no URL display is configured
        public_booking_token:
          type: string
          nullable: true
          description: Public booking token, when Booking with TRMNL is open
        public_booking_url:
          type: string
          nullable: true
        current_booking:
          nullable: true
          allOf:
          - "$ref": "#/components/schemas/RoomBooking"
        upcoming_bookings:
          type: array
          items:
            "$ref": "#/components/schemas/RoomBooking"
