openapi: 3.1.0 info: title: Dash API version: 0.1.0 description: > Single source of truth for the Dash API. The TypeScript client in packages/api-client is generated from this file — do not hand-write API types in the frontend. servers: - url: / paths: /api/healthz: get: operationId: healthz summary: Liveness + database reachability probe responses: "200": description: Service healthy content: application/json: schema: $ref: "#/components/schemas/Health" "503": description: Database unreachable content: application/json: schema: $ref: "#/components/schemas/Health" /api/sections: get: operationId: listSections summary: Full board — sections with nested items and urls, ordered responses: "200": description: Ordered board tree content: application/json: schema: type: array items: $ref: "#/components/schemas/Section" post: operationId: createSection requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SectionInput" responses: "201": description: Created section content: application/json: schema: $ref: "#/components/schemas/Section" /api/sections/{id}: patch: operationId: updateSection parameters: - $ref: "#/components/parameters/id" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SectionPatch" responses: "200": description: Updated section content: application/json: schema: $ref: "#/components/schemas/Section" "404": $ref: "#/components/responses/NotFound" delete: operationId: deleteSection parameters: - $ref: "#/components/parameters/id" responses: "204": description: Section and its items deleted /api/sections/reorder: post: operationId: reorderSection summary: Move a section between two neighbors (fractional position) requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/Reorder" responses: "200": description: Reordered content: application/json: schema: $ref: "#/components/schemas/Section" "404": $ref: "#/components/responses/NotFound" /api/items: post: operationId: createItem requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ItemInput" responses: "201": description: Created item content: application/json: schema: $ref: "#/components/schemas/Item" "400": $ref: "#/components/responses/BadRequest" /api/items/{id}: patch: operationId: updateItem parameters: - $ref: "#/components/parameters/id" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ItemPatch" responses: "200": description: Updated item content: application/json: schema: $ref: "#/components/schemas/Item" "404": $ref: "#/components/responses/NotFound" delete: operationId: deleteItem parameters: - $ref: "#/components/parameters/id" responses: "204": description: Item deleted /api/items/{id}/icon: post: operationId: uploadIcon summary: Upload an icon file; returns the stored icon reference parameters: - $ref: "#/components/parameters/id" requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: type: string format: binary responses: "200": description: Icon stored content: application/json: schema: $ref: "#/components/schemas/Item" "400": $ref: "#/components/responses/BadRequest" /api/items/reorder: post: operationId: reorderItem summary: Move an item between two neighbors, optionally across sections requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ItemReorder" responses: "200": description: Reordered content: application/json: schema: $ref: "#/components/schemas/Item" "404": $ref: "#/components/responses/NotFound" /api/icons/{file}: get: operationId: getIcon summary: Serve an uploaded icon file parameters: - name: file in: path required: true schema: type: string responses: "200": description: Icon bytes "404": $ref: "#/components/responses/NotFound" /api/monitors: get: operationId: listMonitors summary: All monitors with live uptime stats responses: "200": description: Monitors ordered by position content: application/json: schema: type: array items: $ref: "#/components/schemas/MonitorView" post: operationId: createMonitor requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/MonitorInput" responses: "201": description: Created monitor content: application/json: schema: $ref: "#/components/schemas/MonitorView" "400": $ref: "#/components/responses/BadRequest" /api/monitors/{id}: get: operationId: getMonitor parameters: - $ref: "#/components/parameters/id" responses: "200": description: Monitor with stats content: application/json: schema: $ref: "#/components/schemas/MonitorView" "404": $ref: "#/components/responses/NotFound" patch: operationId: updateMonitor summary: Sparse update; `active` toggles pause/resume parameters: - $ref: "#/components/parameters/id" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/MonitorInput" responses: "200": description: Updated monitor content: application/json: schema: $ref: "#/components/schemas/MonitorView" "404": $ref: "#/components/responses/NotFound" delete: operationId: deleteMonitor parameters: - $ref: "#/components/parameters/id" responses: "204": description: Monitor and its heartbeats deleted "404": $ref: "#/components/responses/NotFound" /api/monitors/{id}/heartbeats: get: operationId: monitorHeartbeats parameters: - $ref: "#/components/parameters/id" - name: hours in: query schema: type: integer default: 24 maximum: 720 responses: "200": description: Check history, oldest first content: application/json: schema: type: array items: $ref: "#/components/schemas/Heartbeat" /api/monitors/{id}/check: post: operationId: checkMonitorNow summary: Run one check synchronously parameters: - $ref: "#/components/parameters/id" responses: "200": description: Monitor after the check content: application/json: schema: $ref: "#/components/schemas/MonitorView" "404": $ref: "#/components/responses/NotFound" /api/push/{token}: get: operationId: pushMonitorGet summary: Record an up heartbeat for a push monitor parameters: - name: token in: path required: true schema: type: string - name: msg in: query schema: type: string - name: ping in: query schema: type: integer responses: "200": description: Heartbeat recorded "404": $ref: "#/components/responses/NotFound" post: operationId: pushMonitorPost summary: Record an up heartbeat for a push monitor parameters: - name: token in: path required: true schema: type: string - name: msg in: query schema: type: string - name: ping in: query schema: type: integer responses: "200": description: Heartbeat recorded "404": $ref: "#/components/responses/NotFound" /api/widgets/types: get: operationId: widgetTypes summary: Widget registry metadata — drives the add-widget dialog responses: "200": description: Registered widget types content: application/json: schema: type: array items: $ref: "#/components/schemas/WidgetType" /api/widgets/{id}/data: get: operationId: widgetData summary: Live data for one widget item (~30s server-side cache) parameters: - $ref: "#/components/parameters/id" responses: "200": description: Widget payload; shape depends on config.type content: application/json: schema: $ref: "#/components/schemas/WidgetData" "400": $ref: "#/components/responses/BadRequest" "404": $ref: "#/components/responses/NotFound" "502": description: Upstream service unreachable content: application/json: schema: $ref: "#/components/schemas/Error" /api/status: get: operationId: getStatus summary: Reachability of each service item's primary URL (~60s server cache) responses: "200": description: Status map keyed by item id content: application/json: schema: type: object additionalProperties: $ref: "#/components/schemas/Status" /api/settings: get: operationId: getSettings responses: "200": description: All settings as key/value map content: application/json: schema: type: object additionalProperties: true put: operationId: putSettings summary: Merge settings (keys not sent are preserved) requestBody: required: true content: application/json: schema: type: object additionalProperties: true responses: "200": description: Full settings map after merge content: application/json: schema: type: object additionalProperties: true /api/export: get: operationId: exportBoard summary: Full JSON backup (sections, items, urls, settings) responses: "200": description: Board export content: application/json: schema: $ref: "#/components/schemas/Export" /api/import: post: operationId: importBoard summary: Replace board state from an export payload requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/Export" responses: "200": description: Imported board tree content: application/json: schema: type: array items: $ref: "#/components/schemas/Section" "400": $ref: "#/components/responses/BadRequest" /api/import/external: post: operationId: importExternal summary: > Import a foreign dashboard config — Homepage services.yml, Homarr JSON export, or Dashy conf.yml. Format is auto-detected; the board is replaced and existing settings are preserved. requestBody: required: true content: application/octet-stream: schema: type: string format: binary application/json: schema: type: string format: binary text/yaml: schema: type: string format: binary responses: "200": description: Imported board tree content: application/json: schema: type: array items: $ref: "#/components/schemas/Section" "400": $ref: "#/components/responses/BadRequest" components: parameters: id: name: id in: path required: true schema: type: string responses: NotFound: description: Not found content: application/json: schema: $ref: "#/components/schemas/Error" BadRequest: description: Invalid input content: application/json: schema: $ref: "#/components/schemas/Error" schemas: Health: type: object required: [status] properties: status: type: string example: ok Error: type: object required: [error] properties: error: type: string Url: type: object required: [id, url, label] properties: id: type: string url: type: string format: uri label: type: string description: Tag such as "local" / "external" or a custom label position: type: number UrlInput: type: object required: [url] properties: url: type: string format: uri label: type: string default: "" Item: type: object required: [id, sectionId, kind, name, position, urls] properties: id: type: string sectionId: type: string kind: type: string enum: [service, widget] name: type: string icon: type: string description: "/api/icons/ path or remote URL; empty = letter tile" position: type: number config: type: object nullable: true additionalProperties: true description: Widget config (Phase 2); null for services urls: type: array items: $ref: "#/components/schemas/Url" ItemInput: type: object required: [sectionId, name] properties: sectionId: type: string kind: type: string enum: [service, widget] default: service name: type: string icon: type: string config: type: object nullable: true additionalProperties: true description: Widget config — keys defined by the widget type's fields urls: type: array items: $ref: "#/components/schemas/UrlInput" ItemPatch: type: object properties: name: type: string icon: type: string config: type: object nullable: true additionalProperties: true urls: type: array description: Full replacement of the item's URL list items: $ref: "#/components/schemas/UrlInput" Section: type: object required: [id, name, position, collapsed, items] properties: id: type: string name: type: string position: type: number collapsed: type: boolean items: type: array items: $ref: "#/components/schemas/Item" SectionInput: type: object required: [name] properties: name: type: string SectionPatch: type: object properties: name: type: string collapsed: type: boolean Reorder: type: object required: [id] properties: id: type: string beforeId: type: string description: Neighbor now before the moved entity; null if first afterId: type: string description: Neighbor now after the moved entity; null if last ItemReorder: allOf: - $ref: "#/components/schemas/Reorder" - type: object properties: sectionId: type: string description: Target section (defaults to current) Status: type: object required: [status] properties: status: type: string enum: [up, down] latencyMs: type: integer checkedAt: type: string format: date-time Monitor: type: object required: [id, name, type, active, status] properties: id: type: string name: type: string type: type: string enum: [http, tcp, ping, dns, keyword, json, push] url: type: string hostname: type: string port: type: integer method: type: string headers: type: string description: JSON object of request headers body: type: string keyword: type: string keywordInvert: type: boolean jsonQuery: type: string description: Dot path into a JSON response, e.g. "data.status" expected: type: string dnsType: type: string intervalS: type: integer timeoutS: type: integer retries: type: integer active: type: boolean status: type: string enum: [up, down, pending, paused, maintenance] pushToken: type: string tags: type: array items: type: string notes: type: string position: type: number lastCheck: type: string format: date-time nullable: true createdAt: type: string format: date-time MonitorInput: type: object properties: name: type: string type: type: string enum: [http, tcp, ping, dns, keyword, json, push] url: type: string hostname: type: string port: type: integer method: type: string headers: type: string body: type: string keyword: type: string keywordInvert: type: boolean jsonQuery: type: string expected: type: string dnsType: type: string intervalS: type: integer timeoutS: type: integer retries: type: integer active: type: boolean tags: type: array items: type: string notes: type: string UptimeStats: type: object properties: up: type: integer down: type: integer uptime24h: type: number uptime30d: type: number avgPing24h: type: integer MonitorView: allOf: - $ref: "#/components/schemas/Monitor" - type: object properties: stats: $ref: "#/components/schemas/UptimeStats" Heartbeat: type: object properties: id: type: integer status: type: string enum: [up, down] pingMs: type: integer msg: type: string certExpiry: type: integer checkedAt: type: string format: date-time WidgetField: type: object required: [key, label] properties: key: type: string label: type: string secret: type: boolean description: Render as password input; never logged placeholder: type: string required: type: boolean WidgetType: type: object required: [type, name, fields] properties: type: type: string name: type: string description: type: string local: type: boolean description: Client-rendered (e.g. clock); no /data fetch needed fields: type: array items: $ref: "#/components/schemas/WidgetField" WidgetData: type: object required: [data] properties: data: type: object additionalProperties: true fetchedAt: type: string format: date-time Export: type: object required: [version, sections] properties: version: type: integer settings: type: object additionalProperties: true sections: type: array items: $ref: "#/components/schemas/Section"