{"openapi":"3.0.3","info":{"title":"PulseBoard API","version":"0.1.0","description":"The public /demo contract for a local-first SaaS backend demo covering uptime checks, incidents, webhooks, audit logs, and async workers. Representative public, authentication, workspace, uptime-check, and incident responses have executable schemas. The local operator-only /metrics route is intentionally excluded."},"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"responses":{"Unauthorized":{"description":"Missing, invalid, or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ValidationError":{"description":"Invalid request or unsafe monitoring target","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"Resource not found within the authenticated tenant boundary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Conflict":{"description":"The requested state transition conflicts with current state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ErrorResponse":{"type":"object","additionalProperties":false,"required":["error","requestId"],"properties":{"error":{"type":"string"},"requestId":{"type":"string","minLength":1}}},"LivenessResponse":{"type":"object","additionalProperties":false,"required":["status","service","release","contract","checkedAt"],"properties":{"status":{"type":"string","enum":["ok"]},"service":{"type":"string","enum":["pulseboard-api"]},"release":{"type":"string","minLength":1},"contract":{"type":"string","minLength":1},"checkedAt":{"type":"string","format":"date-time"}}},"ReadinessResponse":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","enum":["ready"]}}},"NotReadyResponse":{"type":"object","additionalProperties":false,"required":["status","requestId"],"properties":{"status":{"type":"string","enum":["not_ready"]},"reason":{"type":"string","enum":["shutting_down"]},"requestId":{"type":"string","minLength":1}}},"Workspace":{"type":"object","additionalProperties":false,"required":["id","name","slug","createdAt","updatedAt"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"WorkspaceListResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Workspace"}}}},"MonitoredService":{"type":"object","additionalProperties":false,"required":["id","name","slug","description","baseUrl","status","createdAt","updatedAt","projectId"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"description":{"type":"string","nullable":true},"baseUrl":{"type":"string","format":"uri"},"status":{"type":"string","enum":["ACTIVE","PAUSED","ARCHIVED"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"projectId":{"type":"string","format":"uuid"}}},"UptimeCheck":{"type":"object","additionalProperties":false,"required":["id","name","description","method","url","expectedStatus","intervalSeconds","timeoutMs","consecutiveFailuresToOpen","consecutiveSuccessesToResolve","isActive","nextRunAt","createdAt","updatedAt","serviceId"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"method":{"type":"string","enum":["GET","HEAD"]},"url":{"type":"string","format":"uri"},"expectedStatus":{"type":"integer","minimum":100,"maximum":599},"intervalSeconds":{"type":"integer","minimum":30,"maximum":86400},"timeoutMs":{"type":"integer","minimum":500,"maximum":30000},"consecutiveFailuresToOpen":{"type":"integer","minimum":1,"maximum":10},"consecutiveSuccessesToResolve":{"type":"integer","minimum":1,"maximum":10},"isActive":{"type":"boolean"},"nextRunAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"serviceId":{"type":"string","format":"uuid"}}},"UptimeCheckResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"$ref":"#/components/schemas/UptimeCheck"}}},"UptimeCheckListResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/UptimeCheck"}}}},"CheckRun":{"type":"object","additionalProperties":false,"required":["id","idempotencyKey","status","statusCode","latencyMs","errorMessage","checkedAt","uptimeCheckId","serviceId"],"properties":{"id":{"type":"string","format":"uuid"},"idempotencyKey":{"type":"string"},"status":{"type":"string","enum":["UP","DOWN","DEGRADED"]},"statusCode":{"type":"integer","nullable":true},"latencyMs":{"type":"integer","nullable":true},"errorMessage":{"type":"string","nullable":true},"checkedAt":{"type":"string","format":"date-time"},"uptimeCheckId":{"type":"string","format":"uuid"},"serviceId":{"type":"string","format":"uuid"}}},"UptimeCheckDetailResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"object","additionalProperties":false,"required":["id","name","description","method","url","expectedStatus","intervalSeconds","timeoutMs","consecutiveFailuresToOpen","consecutiveSuccessesToResolve","isActive","nextRunAt","createdAt","updatedAt","serviceId","checkRuns"],"properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"method":{"type":"string","enum":["GET","HEAD"]},"url":{"type":"string","format":"uri"},"expectedStatus":{"type":"integer","minimum":100,"maximum":599},"intervalSeconds":{"type":"integer","minimum":30,"maximum":86400},"timeoutMs":{"type":"integer","minimum":500,"maximum":30000},"consecutiveFailuresToOpen":{"type":"integer","minimum":1,"maximum":10},"consecutiveSuccessesToResolve":{"type":"integer","minimum":1,"maximum":10},"isActive":{"type":"boolean"},"nextRunAt":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"serviceId":{"type":"string","format":"uuid"},"checkRuns":{"type":"array","items":{"$ref":"#/components/schemas/CheckRun"}}}}}},"IncidentListItem":{"type":"object","additionalProperties":false,"required":["id","title","status","severity","summary","openedAt","acknowledgedAt","resolvedAt","createdAt","updatedAt","serviceId","openIdempotencyKey","resolveIdempotencyKey","service"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"type":"string","enum":["OPEN","ACKNOWLEDGED","RESOLVED"]},"severity":{"type":"string"},"summary":{"type":"string","nullable":true},"openedAt":{"type":"string","format":"date-time"},"acknowledgedAt":{"type":"string","format":"date-time","nullable":true},"resolvedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"serviceId":{"type":"string","format":"uuid"},"openIdempotencyKey":{"type":"string","nullable":true},"resolveIdempotencyKey":{"type":"string","nullable":true},"service":{"$ref":"#/components/schemas/MonitoredService"}}},"NotificationAttempt":{"type":"object","additionalProperties":false,"required":["id","attemptNumber","succeeded","errorMessage","responseStatus","createdAt","notificationId"],"properties":{"id":{"type":"string","format":"uuid"},"attemptNumber":{"type":"integer"},"succeeded":{"type":"boolean"},"errorMessage":{"type":"string","nullable":true},"responseStatus":{"type":"integer","nullable":true},"createdAt":{"type":"string","format":"date-time"},"notificationId":{"type":"string","format":"uuid"}}},"NotificationWithAttempts":{"type":"object","additionalProperties":false,"required":["id","idempotencyKey","channel","status","target","payload","sentAt","errorMessage","attemptCount","cycleAttemptCount","maxAttempts","nextAttemptAt","lastAttemptAt","leaseOwner","leaseExpiresAt","deadLetteredAt","replayedAt","createdAt","updatedAt","incidentId","attempts"],"properties":{"id":{"type":"string","format":"uuid"},"idempotencyKey":{"type":"string"},"channel":{"type":"string","enum":["EMAIL","SLACK","WEBHOOK"]},"status":{"type":"string","enum":["QUEUED","PROCESSING","SENT","FAILED","DEAD_LETTER"]},"target":{"type":"string"},"payload":{},"sentAt":{"type":"string","format":"date-time","nullable":true},"errorMessage":{"type":"string","nullable":true},"attemptCount":{"type":"integer"},"cycleAttemptCount":{"type":"integer"},"maxAttempts":{"type":"integer"},"nextAttemptAt":{"type":"string","format":"date-time"},"lastAttemptAt":{"type":"string","format":"date-time","nullable":true},"leaseOwner":{"type":"string","nullable":true},"leaseExpiresAt":{"type":"string","format":"date-time","nullable":true},"deadLetteredAt":{"type":"string","format":"date-time","nullable":true},"replayedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"incidentId":{"type":"string","format":"uuid","nullable":true},"attempts":{"type":"array","items":{"$ref":"#/components/schemas/NotificationAttempt"}}}},"IncidentListResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IncidentListItem"}}}},"IncidentDetailResponse":{"type":"object","additionalProperties":false,"required":["data"],"properties":{"data":{"type":"object","additionalProperties":false,"required":["id","title","status","severity","summary","openedAt","acknowledgedAt","resolvedAt","createdAt","updatedAt","serviceId","openIdempotencyKey","resolveIdempotencyKey","service","notifications"],"properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"status":{"type":"string","enum":["OPEN","ACKNOWLEDGED","RESOLVED"]},"severity":{"type":"string"},"summary":{"type":"string","nullable":true},"openedAt":{"type":"string","format":"date-time"},"acknowledgedAt":{"type":"string","format":"date-time","nullable":true},"resolvedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"serviceId":{"type":"string","format":"uuid"},"openIdempotencyKey":{"type":"string","nullable":true},"resolveIdempotencyKey":{"type":"string","nullable":true},"service":{"$ref":"#/components/schemas/MonitoredService"},"notifications":{"type":"array","items":{"$ref":"#/components/schemas/NotificationWithAttempts"}}}}}}}},"paths":{"/demo/health/live":{"get":{"security":[],"summary":"Liveness check","responses":{"200":{"description":"Alive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LivenessResponse"}}}}}}},"/demo/health/ready":{"get":{"security":[],"summary":"Readiness check","responses":{"200":{"description":"Dependencies are ready","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReadinessResponse"}}}},"503":{"description":"Draining or a dependency is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotReadyResponse"}}}}}}},"/demo/api/v1/api-keys":{"get":{"summary":"List API keys for the authenticated user","responses":{"200":{"description":"API key metadata list"}}},"post":{"summary":"Create an API key","description":"Returns the plaintext API key only once. Store only the returned value in a secure secret manager.","responses":{"201":{"description":"API key created"}}}},"/demo/api/v1/api-keys/{id}":{"delete":{"summary":"Revoke an API key","responses":{"200":{"description":"API key revoked"},"404":{"description":"Not found"}}}},"/demo/api/v1/workspaces":{"get":{"summary":"List workspaces","responses":{"200":{"description":"Workspace list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"post":{"summary":"Create workspace","responses":{"201":{"description":"Workspace created"}}}},"/demo/api/v1/workspaces/{id}":{"get":{"summary":"Get workspace","responses":{"200":{"description":"Workspace detail"},"404":{"description":"Not found"}}},"patch":{"summary":"Update workspace","responses":{"200":{"description":"Workspace updated"}}},"delete":{"summary":"Delete workspace","responses":{"200":{"description":"Workspace deleted"}}}},"/demo/api/v1/workspaces/{workspaceId}/projects":{"get":{"summary":"List projects","responses":{"200":{"description":"Project list"}}},"post":{"summary":"Create project","responses":{"201":{"description":"Project created"}}}},"/demo/api/v1/projects/{id}":{"get":{"summary":"Get project","responses":{"200":{"description":"Project detail"},"404":{"description":"Not found"}}},"patch":{"summary":"Update project","responses":{"200":{"description":"Project updated"}}},"delete":{"summary":"Delete project","responses":{"200":{"description":"Project deleted"}}}},"/demo/api/v1/projects/{projectId}/services":{"get":{"summary":"List services","responses":{"200":{"description":"Service list"}}},"post":{"summary":"Create service","responses":{"201":{"description":"Service created"}}}},"/demo/api/v1/services/{id}":{"get":{"summary":"Get service","responses":{"200":{"description":"Service detail"},"404":{"description":"Not found"}}},"patch":{"summary":"Update service","responses":{"200":{"description":"Service updated"}}},"delete":{"summary":"Archive service","responses":{"200":{"description":"Service archived"}}}},"/demo/api/v1/services/{serviceId}/uptime-checks":{"get":{"summary":"List uptime checks","responses":{"200":{"description":"Uptime check list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UptimeCheckListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"summary":"Create uptime check","description":"Only public HTTP(S) targets are accepted. PulseBoard rejects local, private, reserved, metadata, mixed-DNS, and credential-bearing targets before queueing a check.","responses":{"201":{"description":"Uptime check created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UptimeCheckResponse"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/demo/api/v1/uptime-checks/{id}":{"get":{"summary":"Get uptime check","responses":{"200":{"description":"Uptime check detail with recent runs","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UptimeCheckDetailResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update uptime check","description":"A changed URL is subject to the same public HTTP(S) target validation as creation.","responses":{"200":{"description":"Uptime check updated"},"400":{"$ref":"#/components/responses/ValidationError"}}},"delete":{"summary":"Disable uptime check","responses":{"200":{"description":"Uptime check disabled"}}}},"/demo/api/v1/incidents":{"get":{"summary":"List incidents","responses":{"200":{"description":"Incident list with monitored services","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncidentListResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/demo/api/v1/incidents/{id}":{"get":{"summary":"Get incident","responses":{"200":{"description":"Incident detail with notification attempts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncidentDetailResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"summary":"Update incident status","responses":{"200":{"description":"Incident updated"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/demo/api/v1/notifications/{id}/replay":{"post":{"summary":"Replay a failed or dead-letter notification","responses":{"202":{"description":"Notification requeued"},"409":{"description":"Notification is not replayable"}}}},"/demo/api/v1/webhooks/events":{"post":{"summary":"Ingest webhook event","responses":{"202":{"description":"Webhook accepted"}}}},"/demo/api/v1/usage-metrics":{"get":{"summary":"List usage metrics","responses":{"200":{"description":"Usage metric list"}}}},"/demo/api/v1/audit-logs":{"get":{"summary":"List audit logs","responses":{"200":{"description":"Audit log list"}}}}}}