{"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\ntoken. Create a key on your account settings page and pick what it may do: read,\ncontent, devices, delete, profile or apps; an app asks for the same scopes and the user\npicks them (see https://trmnl.com/auth.md). An operation outside\nthem answers 403 naming the capability it needs. A key can also be limited to some\ndevices and plugin settings, and then answers 403 naming what it was not granted. The\nlegacy account API key reaches only the endpoints it always had, and answers 403 on the\nrest.\n\nThis is a plain OpenAPI 3 document, so a client can be generated from it, or read\nstraight off it at runtime. The TRMNL CLI (https://github.com/usetrmnl/cli) takes the second\nroute, so every operation here is a command. It signs in through your browser, no key needed:\n\n```\nbrew install usetrmnl/tap/trmnl\ntrmnl list-devices\n```\n\nTo build a plugin, use trmnlp instead: https://github.com/usetrmnl/trmnlp. It serves\nyour markup locally with live reload and syncs it with your account.\n\nOver MCP (https://trmnl.com/mcp) an agent runs these same operations as account\ntools. An OAuth connection holds the capabilities its user ticked at consent (read,\ncontent, devices, delete, profile) and may be limited to some devices and plugin\nsettings; an operation outside them answers 403 naming what is missing. See\nhttps://trmnl.com/auth.md.\n"},"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,\nthen the HTTP status code will be 200 but the `status` code in the response will be 404.\n","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).\n","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.\n\nPlugin poll requests will only originate from these IPs.\n","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/markup":{"post":{"summary":"Render Liquid template","operationId":"renderMarkup","tags":["Markup"],"parameters":[],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]}}}}}}},"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/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 walk-up 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 walk-up 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, walk-up booking 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 walk-up visitor","tags":["Public booking"],"operationId":"createPublicBooking","description":"Public: no bearer token. The same rules as the walk-up 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, walk-up booking 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 walk-up 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, walk-up booking 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 walk-up 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, walk-up booking 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",null]},"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":"Walk-up QR booking"},"dark_mode":{"type":"boolean"},"show_company_logo":{"type":"boolean"},"daily_public_booking_url_rotation":{"type":"boolean"},"time_format":{"type":"string","nullable":true,"enum":[null,"24_hour","12_hour"],"description":"null detects it from the time zone"},"walk_up_calendar":{"type":"boolean","description":"Upcoming bookings on the walk-up 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 \"\u003ckind\u003e:\u003cnumber\u003e\", 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"}},"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 and show_booking_qr. A Google or Microsoft calendar keeps its provider name and takes override_name and show_booking_qr.","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"}}}}},"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","description":"Device id","required":true,"schema":{"type":"integer"}}],"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"},"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"},"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","description":"Device id","required":true,"schema":{"type":"integer"}}],"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"},"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/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"}}}},"always_active":{"type":"boolean","example":false,"description":"True when no window is 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 clears 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"}}}},"always_active":{"type":"boolean","example":false,"description":"True when no window is 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"}}}}},"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",null]},"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\nshared.liquid.\n\nThis endpoint is available for unauthenticated requests.\n\nWhen unauthenticated, any published recipe may be archived.\n\nWhen authenticated, the requesting user's private plugins are also archivable.\n\nAn archive of another user's recipe leaves polling_headers and polling_body empty.\n","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":"---\nstrategy: static\nname: Mine\n","full.liquid":"\u003cdiv\u003e{{ title }}\u003c/div\u003e"}}},"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: \"\u003clevel\u003e, \u003ctimestamp\u003e, \u003cmessage\u003e\" }"}}}}}}}}}}}},"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] \u003cname\u003e\", 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\nresolution and is not sized for a panel; a device is driven through /api/display.\n","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":"Matches the plugin name","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/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:\u003cid\u003e or Mashup:\u003cid\u003e, 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},"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, \"\u003chue\u003e-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 \u003crole\u003e_case (\"as-written\"/\"uppercase\"/\"lowercase\") and \u003crole\u003e_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 \u0026 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":"\"\u003ckind\u003e:\u003cnumber\u003e\", 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"},"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":[null,"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"},"public_booking_token":{"type":"string","nullable":true,"description":"Walk-up QR booking token, when walk-up booking 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"},"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"},"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":"Walk-up QR booking"},"dark_mode":{"type":"boolean"},"show_company_logo":{"type":"boolean"},"daily_public_booking_url_rotation":{"type":"boolean"},"time_format":{"type":"string","nullable":true,"enum":[null,"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 walk-up 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":"\"\u003ckind\u003e:\u003cnumber\u003e\", 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"},"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":[null,"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"},"public_booking_token":{"type":"string","nullable":true,"description":"Walk-up QR booking token, when walk-up booking 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"}}}}}}}