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/domains: get: operationId: listDomains summary: All tracked domains with expiry countdowns responses: "200": description: Domains ordered by position content: application/json: schema: type: array items: $ref: "#/components/schemas/DomainView" post: operationId: createDomain summary: Track a domain; runs the first WHOIS/DNS/SSL lookup inline requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DomainInput" responses: "201": description: Domain after the initial lookup content: application/json: schema: $ref: "#/components/schemas/DomainView" "400": $ref: "#/components/responses/BadRequest" "409": description: Domain already tracked /api/domains/{id}: get: operationId: getDomain parameters: - $ref: "#/components/parameters/id" responses: "200": description: Domain detail content: application/json: schema: $ref: "#/components/schemas/DomainView" "404": $ref: "#/components/responses/NotFound" patch: operationId: updateDomain summary: Sparse update — settings fields only; lookup data is refreshed separately parameters: - $ref: "#/components/parameters/id" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DomainInput" responses: "200": description: Updated domain content: application/json: schema: $ref: "#/components/schemas/DomainView" "404": $ref: "#/components/responses/NotFound" delete: operationId: deleteDomain parameters: - $ref: "#/components/parameters/id" responses: "204": description: Domain and its check history deleted "404": $ref: "#/components/responses/NotFound" /api/domains/{id}/refresh: post: operationId: refreshDomain summary: Re-run WHOIS/RDAP + DNS + TLS + geo lookup now parameters: - $ref: "#/components/parameters/id" responses: "200": description: Domain after the refresh content: application/json: schema: $ref: "#/components/schemas/DomainView" "404": $ref: "#/components/responses/NotFound" /api/domains/{id}/checks: get: operationId: domainChecks summary: Snapshot history for change detection parameters: - $ref: "#/components/parameters/id" - name: limit in: query schema: type: integer default: 50 maximum: 500 responses: "200": description: Snapshots, newest first content: application/json: schema: type: array items: $ref: "#/components/schemas/DomainCheck" /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/notify/test: post: operationId: notifyTest summary: Send a test notification to the configured webhook description: Requires the `notify_webhook` settings key to hold an http(s) URL. responses: "200": description: Test notification dispatched "400": $ref: "#/components/responses/BadRequest" /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 Domain: type: object required: [id, name, active] properties: id: type: string name: type: string tld: type: string active: type: boolean autoRenew: type: boolean alertDaysBefore: type: integer tags: type: array items: type: string notes: type: string expiryDate: type: string format: date-time nullable: true creationDate: type: string format: date-time nullable: true updatedDate: type: string format: date-time nullable: true registrarName: type: string registrarId: type: string registrarUrl: type: string registryDomainId: type: string dnssec: type: string statuses: type: array items: type: string privacyEnabled: type: boolean transferLock: type: boolean registrantName: type: string registrantOrg: type: string registrantCountry: type: string abuseEmail: type: string nameServers: type: array items: type: string mxRecords: type: array items: type: string txtRecords: type: array items: type: string cname: type: string ipv4: type: array items: type: string ipv6: type: array items: type: string sslIssuer: type: string sslValidFrom: type: string format: date-time nullable: true sslValidTo: type: string format: date-time nullable: true sslSubject: type: string sslFingerprint: type: string sslKeySize: type: integer sslSigAlgo: type: string sslAltNames: type: array items: type: string hostCountry: type: string hostCountryCode: type: string hostRegion: type: string hostCity: type: string hostIsp: type: string hostOrg: type: string hostAs: type: string hostLat: type: number hostLon: type: number dnsProvider: type: string emailProvider: type: string hostingProvider: type: string caProvider: type: string headers: type: object additionalProperties: type: string faviconUrl: type: string lookupError: type: string intervalH: type: integer lastChecked: type: string format: date-time nullable: true position: type: number createdAt: type: string format: date-time DomainInput: type: object properties: name: type: string active: type: boolean autoRenew: type: boolean alertDaysBefore: type: integer intervalH: type: integer tags: type: array items: type: string notes: type: string DomainView: allOf: - $ref: "#/components/schemas/Domain" - type: object properties: daysUntilExpiry: type: integer nullable: true sslDaysUntilExpiry: type: integer nullable: true expiring: type: boolean sslExpiring: type: boolean DomainCheck: type: object properties: id: type: integer expiryDate: type: string format: date-time nullable: true sslValidTo: type: string format: date-time nullable: true ipv4: type: array items: type: string nameServers: type: array items: type: string 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"