diff --git a/apps/server/.env.test b/apps/server/.env.test index 9a44b674..1f541dd8 100644 --- a/apps/server/.env.test +++ b/apps/server/.env.test @@ -22,3 +22,5 @@ API_RATE_LIMIT_PER_MINUTE=1000000 API_RATE_LIMIT_BURST_PER_10S=1000000 API_RATE_LIMIT_WRITES_PER_MINUTE=1000000 API_RATE_LIMIT_PUBLIC_PER_MINUTE=1000000 +# Incident management is behind a workspace allowlist until launch. +OPENSTATUS_FEATURES=incident-management diff --git a/apps/server/src/routes/slack/background.ts b/apps/server/src/libs/background.ts similarity index 91% rename from apps/server/src/routes/slack/background.ts rename to apps/server/src/libs/background.ts index 8b21659d..d6fdf87e 100644 --- a/apps/server/src/routes/slack/background.ts +++ b/apps/server/src/libs/background.ts @@ -5,7 +5,7 @@ const logger = getLogger("api-server"); /** * Work that runs after the HTTP response has been sent. * - * Slack gives us 3 seconds to acknowledge an event, an interaction or a slash + * Slack, for example, gives us 3 seconds to acknowledge an event, an interaction or a slash * command. Anything slower and the user sees a timeout warning — on a click * that in fact succeeded. Approving a status report writes to the DB and then * fans out to every subscriber, so it routinely outlives that window: ack @@ -27,7 +27,7 @@ export function runInBackground( const task: Promise = Promise.resolve() .then(work) .catch((error: unknown) => { - logger.error(`slack background task failed: ${label}`, { + logger.error(`background task failed: ${label}`, { error, ...context, }); diff --git a/apps/server/src/libs/test/doubles/emails.mock.ts b/apps/server/src/libs/test/doubles/emails.mock.ts new file mode 100644 index 00000000..d6c09584 --- /dev/null +++ b/apps/server/src/libs/test/doubles/emails.mock.ts @@ -0,0 +1,22 @@ +// Test double for @openstatus/emails, swapped in via --import-map: records the +// incident commander email instead of calling Resend; everything else is real. +import { sendIncidentCommander as realSendIncidentCommander } from "@openstatus/emails-real"; + +export * from "@openstatus/emails-real"; + +type IncidentCommanderEmail = Parameters[0]; + +const g = globalThis as { + __incidentCommanderEmails?: IncidentCommanderEmail[]; +}; +if (!g.__incidentCommanderEmails) g.__incidentCommanderEmails = []; + +export const incidentCommanderEmails: IncidentCommanderEmail[] = + g.__incidentCommanderEmails; + +export function sendIncidentCommander( + req: IncidentCommanderEmail, +): Promise { + incidentCommanderEmails.push(req); + return Promise.resolve(); +} diff --git a/apps/server/src/libs/test/doubles/slack-web-api.mock.ts b/apps/server/src/libs/test/doubles/slack-web-api.mock.ts index dc301130..8ce020ee 100644 --- a/apps/server/src/libs/test/doubles/slack-web-api.mock.ts +++ b/apps/server/src/libs/test/doubles/slack-web-api.mock.ts @@ -85,6 +85,31 @@ export class WebClient { s.calls.push({ method: "conversations.info", args }); return s.conversationsInfoImpl(args); }, + create: (args: Record) => { + s.calls.push({ method: "conversations.create", args }); + return Promise.resolve({ + ok: true, + channel: { id: "C_INCIDENT", name: args.name }, + }); + }, + invite: (args: Record) => { + s.calls.push({ method: "conversations.invite", args }); + return Promise.resolve({ ok: true }); + }, + setTopic: (args: Record) => { + s.calls.push({ method: "conversations.setTopic", args }); + return Promise.resolve({ ok: true }); + }, + archive: (args: Record) => { + s.calls.push({ method: "conversations.archive", args }); + return Promise.resolve({ ok: true }); + }, + }; + pins = { + add: (args: Record) => { + s.calls.push({ method: "pins.add", args }); + return Promise.resolve({ ok: true }); + }, }; agents = { sessions: { @@ -113,6 +138,10 @@ export class WebClient { s.calls.push({ method: "users.info", args }); return s.usersInfoImpl(args); }, + lookupByEmail: (args: Record) => { + s.calls.push({ method: "users.lookupByEmail", args }); + return Promise.reject(new Error("users_not_found")); + }, }; views = { publish: (args: Record) => { diff --git a/apps/server/src/routes/rpc/adapter.test.ts b/apps/server/src/routes/rpc/adapter.test.ts index 72e7e134..c46fed3f 100644 --- a/apps/server/src/routes/rpc/adapter.test.ts +++ b/apps/server/src/routes/rpc/adapter.test.ts @@ -1,6 +1,7 @@ import { Code, ConnectError } from "@connectrpc/connect"; import type { Workspace } from "@openstatus/db/src/schema"; import { + ConflictError, ForbiddenError, NotFoundError, type ServiceContext, @@ -92,6 +93,13 @@ describe("toConnectError", () => { expect((err as ConnectError).code).toBe(Code.InvalidArgument); }); + test("ConflictError → ConnectError(FailedPrecondition)", () => { + const err = captureThrow(() => + toConnectError(new ConflictError("Incident #1 is closed")), + ); + expect((err as ConnectError).code).toBe(Code.FailedPrecondition); + }); + test("re-throws an existing ConnectError unchanged", () => { const original = new ConnectError("custom", Code.Aborted); const err = captureThrow(() => toConnectError(original)); diff --git a/apps/server/src/routes/rpc/adapter.ts b/apps/server/src/routes/rpc/adapter.ts index 9e364a93..930639a1 100644 --- a/apps/server/src/routes/rpc/adapter.ts +++ b/apps/server/src/routes/rpc/adapter.ts @@ -49,7 +49,7 @@ export function toConnectError(err: unknown): never { case "UNAUTHORIZED": throw new ConnectError(err.message, Code.Unauthenticated); case "CONFLICT": - throw new ConnectError(err.message, Code.InvalidArgument); + throw new ConnectError(err.message, Code.FailedPrecondition); case "VALIDATION": throw new ConnectError(err.message, Code.InvalidArgument); case "LIMIT_EXCEEDED": diff --git a/apps/server/src/routes/rpc/handlers/incident/__tests__/incident.test.ts b/apps/server/src/routes/rpc/handlers/incident/__tests__/incident.test.ts new file mode 100644 index 00000000..9358fe1a --- /dev/null +++ b/apps/server/src/routes/rpc/handlers/incident/__tests__/incident.test.ts @@ -0,0 +1,979 @@ +import { + type DescMessage, + fromJson, + type JsonObject, + type JsonValue, + type MessageShape, +} from "@bufbuild/protobuf"; +import { db, eq } from "@openstatus/db"; +import { + incident, + integration, + oauthClient, + oauthGrant, + page, + statusReport, +} from "@openstatus/db/src/schema"; +import { + addUserToWorkspace, + createIncident, + createTestWorkspace, + createUser, +} from "@openstatus/db/src/test/factories"; +import { + AddIncidentNoteResponseSchema, + ApprovePostmortemResponseSchema, + CloseIncidentResponseSchema, + DeclareIncidentResponseSchema, + DeleteIncidentResponseSchema, + GetIncidentResponseSchema, + GetPostmortemResponseSchema, + IncidentEventType, + IncidentSeverity, + IncidentStatus, + LinkStatusReportResponseSchema, + ListIncidentsResponseSchema, + PostmortemAuthor, + PostmortemStatus, + SetIncidentStatusResponseSchema, + UnlinkStatusReportResponseSchema, + UpdateIncidentResponseSchema, + UpdatePostmortemResponseSchema, +} from "@openstatus/proto/incident/v1"; +import { + CreateStatusReportResponseSchema, + GetStatusReportResponseSchema, + ListStatusReportsResponseSchema, +} from "@openstatus/proto/status_report/v1"; +import { SLACK_BOT_SCOPES } from "@openstatus/services/integration"; +import { expect } from "@std/expect"; +import { + afterAll, + beforeAll, + beforeEach, + describe, + test, +} from "@std/testing/bdd"; + +import { settleBackgroundTasks } from "@/libs/background"; +import { incidentCommanderEmails } from "@/libs/test/doubles/emails.mock"; +import { slackTestState } from "@/libs/test/doubles/slack-test-state"; + +import { app } from "../../../../../index"; + +const TEST_PREFIX = "rpc-incident-test"; + +type Auth = Record; + +function post( + service: string, + method: string, + body: JsonObject, + headers: Auth, +) { + return app.request(`/rpc/${service}/${method}`, { + method: "POST", + headers: { "Content-Type": "application/json", ...headers }, + body: JSON.stringify(body), + }); +} + +const INCIDENT = "openstatus.incident.v1.IncidentService"; +const STATUS_REPORT = "openstatus.status_report.v1.StatusReportService"; + +async function rpc( + schema: T, + method: string, + body: JsonObject, + headers: Auth, + service = INCIDENT, +): Promise> { + const res = await post(service, method, body, headers); + const json: JsonValue = await res.json(); + if (res.status !== 200) { + throw new Error(`${method} → ${res.status} ${JSON.stringify(json)}`); + } + return fromJson(schema, json); +} + +async function rpcError( + method: string, + body: JsonObject, + headers: Auth, + service = INCIDENT, +): Promise<{ status: number; code: string }> { + const res = await post(service, method, body, headers); + const json: { code?: string } = await res.json(); + return { status: res.status, code: json.code ?? "" }; +} + +async function sha256Hex(input: string): Promise { + const digest = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(input), + ); + return Array.from(new Uint8Array(digest)) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +let workspaceId: number; +let otherWorkspaceId: number; +let pageId: number; +let ownerEmail: string; +let adminEmail: string; +let memberEmail: string; +let clientId: string; + +let owner: Auth; +let admin: Auth; +let member: Auth; +// Dev keys resolve to the workspace with no creator behind them. +let keyWithoutCreator: Auth; + +async function tokenFor(userId: number): Promise { + const token = `os_oat_${crypto.randomUUID().replaceAll("-", "")}`; + const later = new Date(Date.now() + 60 * 60 * 1000); + await db.insert(oauthGrant).values({ + clientId, + userId, + workspaceId, + scope: ["write"], + accessTokenHash: await sha256Hex(token), + accessTokenExpiresAt: later, + refreshTokenHash: await sha256Hex(`${token}-refresh`), + refreshTokenExpiresAt: later, + }); + return { Authorization: `Bearer ${token}` }; +} + +beforeAll(async () => { + const fixture = await createTestWorkspace({ plan: "team" }); + workspaceId = fixture.workspace.id; + ownerEmail = fixture.user.email as string; + otherWorkspaceId = (await createTestWorkspace({ plan: "team" })).workspace.id; + + const adminUser = await createUser(); + const memberUser = await createUser(); + adminEmail = adminUser.email as string; + memberEmail = memberUser.email as string; + await addUserToWorkspace(adminUser.id, workspaceId, "admin"); + await addUserToWorkspace(memberUser.id, workspaceId, "member"); + + clientId = `${TEST_PREFIX}-${workspaceId}`; + await db.insert(oauthClient).values({ + clientId, + redirectUris: ["http://localhost/callback"], + }); + owner = await tokenFor(fixture.user.id); + admin = await tokenFor(adminUser.id); + member = await tokenFor(memberUser.id); + keyWithoutCreator = { "x-openstatus-key": String(workspaceId) }; + + const row = await db + .insert(page) + .values({ + workspaceId, + title: `${TEST_PREFIX}-page`, + description: "", + slug: `${TEST_PREFIX}-${workspaceId}`, + customDomain: "", + }) + .returning() + .get(); + pageId = row.id; + + await db.insert(integration).values({ + name: "slack-agent", + workspaceId, + externalId: "T_RPC", + credential: { botToken: "xoxb-test", botUserId: "UBOT" }, + data: { teamId: "T_RPC", scopes: SLACK_BOT_SCOPES.join(",") }, + }); +}); + +afterAll(async () => { + await db.delete(incident).where(eq(incident.workspaceId, workspaceId)); + await db.delete(incident).where(eq(incident.workspaceId, otherWorkspaceId)); + await db + .delete(statusReport) + .where(eq(statusReport.workspaceId, workspaceId)); + await db.delete(page).where(eq(page.workspaceId, workspaceId)); + await db.delete(integration).where(eq(integration.workspaceId, workspaceId)); + await db.delete(oauthClient).where(eq(oauthClient.clientId, clientId)); +}); + +beforeEach(() => { + slackTestState.calls = []; + incidentCommanderEmails.length = 0; +}); + +async function declare( + headers: Auth = owner, + body: JsonObject = {}, +): Promise { + const res = await rpc( + DeclareIncidentResponseSchema, + "DeclareIncident", + { + title: `${TEST_PREFIX} API down`, + severity: "INCIDENT_SEVERITY_MAJOR", + ...body, + }, + headers, + ); + return res.incident?.id ?? ""; +} + +function setStatus( + id: string, + status: string, + headers: Auth = owner, + note?: string, +) { + return rpc( + SetIncidentStatusResponseSchema, + "SetIncidentStatus", + note === undefined ? { id, status } : { id, status, note }, + headers, + ); +} + +async function get(id: string, headers: Auth = owner) { + const res = await rpc( + GetIncidentResponseSchema, + "GetIncident", + { id }, + headers, + ); + if (!res.incident) throw new Error("no incident in response"); + return res.incident; +} + +async function bindChannel(id: string) { + await db + .update(incident) + .set({ slackTeamId: "T_RPC", slackChannelId: `C_${id}` }) + .where(eq(incident.id, Number(id))); +} + +function slackCalls(method: string) { + return slackTestState.calls.filter((c) => c.method === method); +} + +async function createReport(headers: Auth, incidentId?: string) { + return rpc( + CreateStatusReportResponseSchema, + "CreateStatusReport", + { + title: `${TEST_PREFIX} report`, + status: "STATUS_REPORT_STATUS_INVESTIGATING", + message: "Looking into it", + date: new Date().toISOString(), + pageId: String(pageId), + ...(incidentId === undefined ? {} : { incidentId }), + }, + headers, + STATUS_REPORT, + ); +} + +const ID_METHODS: Array<[string, JsonObject]> = [ + ["GetIncident", {}], + ["UpdateIncident", { title: "x" }], + ["SetIncidentStatus", { status: "INCIDENT_STATUS_MITIGATED" }], + ["AddIncidentNote", { message: "x" }], + ["LinkStatusReport", { statusReportId: "1" }], + ["UnlinkStatusReport", {}], + ["CloseIncident", { skipPostmortem: true }], + ["DeleteIncident", {}], + ["GetPostmortem", {}], + ["UpdatePostmortem", { content: "x" }], + ["ApprovePostmortem", {}], +]; + +function idField(method: string): string { + return method.endsWith("Postmortem") ? "incidentId" : "id"; +} + +describe("IncidentService: common cases", () => { + test("rejects a request without a key", async () => { + const res = await post(INCIDENT, "ListIncidents", {}, {}); + expect(res.status).toBe(401); + }); + + for (const [method, extra] of ID_METHODS) { + test(`${method}: unknown id is not_found`, async () => { + const err = await rpcError( + method, + { [idField(method)]: "999999999", ...extra }, + owner, + ); + expect(err.code).toBe("not_found"); + }); + + test(`${method}: another workspace's incident is not_found`, async () => { + const theirs = await createIncident(otherWorkspaceId, { + title: `${TEST_PREFIX}-theirs`, + }); + const err = await rpcError( + method, + { [idField(method)]: String(theirs.id), ...extra }, + owner, + ); + expect(err.code).toBe("not_found"); + }); + + test(`${method}: a non-numeric id is invalid_argument`, async () => { + const err = await rpcError( + method, + { [idField(method)]: "abc", ...extra }, + owner, + ); + expect(err.code).toBe("invalid_argument"); + }); + + test(`${method}: an empty id is invalid_argument`, async () => { + const err = await rpcError( + method, + { [idField(method)]: "", ...extra }, + owner, + ); + expect(err.code).toBe("invalid_argument"); + }); + } + + test("DeclareIncident validates its input", async () => { + expect( + ( + await rpcError( + "DeclareIncident", + { title: "", severity: "INCIDENT_SEVERITY_MAJOR" }, + owner, + ) + ).code, + ).toBe("invalid_argument"); + expect( + (await rpcError("DeclareIncident", { title: "x" }, owner)).code, + ).toBe("invalid_argument"); + expect( + ( + await rpcError( + "DeclareIncident", + { + title: "x", + severity: "INCIDENT_SEVERITY_MAJOR", + startedAt: "yesterday", + }, + owner, + ) + ).code, + ).toBe("invalid_argument"); + expect( + ( + await rpcError( + "DeclareIncident", + { + title: "x", + severity: "INCIDENT_SEVERITY_MAJOR", + commanderEmail: "not-an-email", + }, + owner, + ) + ).code, + ).toBe("invalid_argument"); + }); +}); + +describe("IncidentService: lifecycle", () => { + test("declare → note → mitigate → resolve → postmortem → approve and close", async () => { + const id = await declare(); + const declared = await get(id); + expect(declared.status).toBe(IncidentStatus.OPEN); + expect(declared.severity).toBe(IncidentSeverity.MAJOR); + expect(declared.commander).toBeUndefined(); + expect(declared.declaredBy?.email).toBe(ownerEmail); + expect(declared.deletable).toBe(true); + expect(declared.allowedTransitions).toEqual([ + IncidentStatus.MITIGATED, + IncidentStatus.RESOLVED, + IncidentStatus.CANCELED, + ]); + + const note = await rpc( + AddIncidentNoteResponseSchema, + "AddIncidentNote", + { id, message: "Rolled back the deploy" }, + owner, + ); + expect(note.event?.type).toBe(IncidentEventType.NOTE); + expect(note.event?.message).toBe("Rolled back the deploy"); + expect(note.event?.createdBy?.email).toBe(ownerEmail); + + const mitigated = await setStatus(id, "INCIDENT_STATUS_MITIGATED"); + expect(mitigated.incident?.status).toBe(IncidentStatus.MITIGATED); + expect(mitigated.incident?.mitigatedAt).toBeDefined(); + expect(mitigated.incident?.deletable).toBe(false); + expect(mitigated.incident?.events).toEqual([]); + + const resolved = await setStatus( + id, + "INCIDENT_STATUS_RESOLVED", + owner, + "All good", + ); + expect(resolved.incident?.status).toBe(IncidentStatus.RESOLVED); + expect(resolved.incident?.resolvedBy?.email).toBe(ownerEmail); + + const pm = await rpc( + UpdatePostmortemResponseSchema, + "UpdatePostmortem", + { incidentId: id, content: "## Summary\nIt broke." }, + owner, + ); + expect(pm.postmortem?.status).toBe(PostmortemStatus.DRAFT); + expect(pm.postmortem?.draftedBy).toBe(PostmortemAuthor.USER); + + const approved = await rpc( + ApprovePostmortemResponseSchema, + "ApprovePostmortem", + { incidentId: id, close: true }, + owner, + ); + expect(approved.postmortem?.status).toBe(PostmortemStatus.APPROVED); + expect(approved.postmortem?.approvedBy?.email).toBe(ownerEmail); + expect(approved.incident?.closedAt).toBeDefined(); + expect(approved.incident?.allowedTransitions).toEqual([]); + + const err = await rpcError( + "AddIncidentNote", + { id, message: "late" }, + owner, + ); + expect(err.code).toBe("failed_precondition"); + + const full = await get(id); + const types = full.events.map((e) => e.type); + expect(types[0]).toBe(IncidentEventType.CLOSED); + expect(types.at(-1)).toBe(IncidentEventType.DECLARED); + expect(types).toContain(IncidentEventType.NOTE); + expect(types).toContain(IncidentEventType.RESOLVED); + const resolvedEvent = full.events.find( + (e) => e.type === IncidentEventType.RESOLVED, + ); + expect(resolvedEvent?.message).toContain("All good"); + }); + + test("cancel closes the incident", async () => { + const id = await declare(); + const canceled = await setStatus(id, "INCIDENT_STATUS_CANCELED"); + expect(canceled.incident?.status).toBe(IncidentStatus.CANCELED); + expect(canceled.incident?.closedAt).toBeDefined(); + expect(canceled.incident?.allowedTransitions).toEqual([]); + }); + + test("the same status or a forbidden transition is failed_precondition", async () => { + const id = await declare(); + expect( + ( + await rpcError( + "SetIncidentStatus", + { id, status: "INCIDENT_STATUS_OPEN" }, + owner, + ) + ).code, + ).toBe("failed_precondition"); + await setStatus(id, "INCIDENT_STATUS_RESOLVED"); + expect( + ( + await rpcError( + "SetIncidentStatus", + { id, status: "INCIDENT_STATUS_MITIGATED" }, + owner, + ) + ).code, + ).toBe("failed_precondition"); + }); + + test("update edits fields and clears the summary", async () => { + const id = await declare(owner, { summary: "first" }); + const updated = await rpc( + UpdateIncidentResponseSchema, + "UpdateIncident", + { + id, + title: `${TEST_PREFIX} renamed`, + severity: "INCIDENT_SEVERITY_CRITICAL", + startedAt: "2026-01-01T00:00:00Z", + }, + owner, + ); + expect(updated.incident?.title).toBe(`${TEST_PREFIX} renamed`); + expect(updated.incident?.severity).toBe(IncidentSeverity.CRITICAL); + expect(updated.incident?.startedAt).toBe("2026-01-01T00:00:00.000Z"); + expect(updated.incident?.summary).toBe("first"); + + const cleared = await rpc( + UpdateIncidentResponseSchema, + "UpdateIncident", + { id, clearSummary: true }, + owner, + ); + expect(cleared.incident?.summary).toBeUndefined(); + expect( + ( + await rpcError( + "UpdateIncident", + { id, summary: "x", clearSummary: true }, + owner, + ) + ).code, + ).toBe("invalid_argument"); + }); +}); + +describe("IncidentService: commander", () => { + test("declare assigns by email and emails the commander", async () => { + const id = await declare(owner, { commanderEmail: memberEmail }); + expect((await get(id)).commander?.email).toBe(memberEmail); + await settleBackgroundTasks(); + expect(incidentCommanderEmails).toHaveLength(1); + expect(incidentCommanderEmails[0]?.to).toBe(memberEmail); + expect(incidentCommanderEmails[0]?.url).toContain(`/incidents/${id}`); + }); + + test("no email when the commander is the key's creator", async () => { + await declare(member, { commanderEmail: memberEmail }); + await settleBackgroundTasks(); + expect(incidentCommanderEmails).toHaveLength(0); + }); + + test("no commander, no email", async () => { + const id = await declare(); + expect((await get(id)).commander).toBeUndefined(); + await settleBackgroundTasks(); + expect(incidentCommanderEmails).toHaveLength(0); + }); + + test("an email that is not a member is invalid_argument", async () => { + const err = await rpcError( + "DeclareIncident", + { + title: "x", + severity: "INCIDENT_SEVERITY_MINOR", + commanderEmail: "nobody@example.com", + }, + owner, + ); + expect(err.code).toBe("invalid_argument"); + }); + + test("update reassigns and clears the commander", async () => { + const id = await declare(); + const assigned = await rpc( + UpdateIncidentResponseSchema, + "UpdateIncident", + { id, commanderEmail: adminEmail }, + owner, + ); + expect(assigned.incident?.commander?.email).toBe(adminEmail); + await settleBackgroundTasks(); + expect(incidentCommanderEmails.map((e) => e.to)).toEqual([adminEmail]); + + const cleared = await rpc( + UpdateIncidentResponseSchema, + "UpdateIncident", + { id, clearCommander: true }, + owner, + ); + expect(cleared.incident?.commander).toBeUndefined(); + expect( + ( + await rpcError( + "UpdateIncident", + { id, commanderEmail: adminEmail, clearCommander: true }, + owner, + ) + ).code, + ).toBe("invalid_argument"); + }); +}); + +describe("IncidentService: roles", () => { + async function resolvedIncident(body: JsonObject = {}) { + const id = await declare(owner, body); + await setStatus(id, "INCIDENT_STATUS_RESOLVED"); + return id; + } + + test("close needs an owner, an admin or the commander", async () => { + const id = await resolvedIncident(); + expect( + (await rpcError("CloseIncident", { id, skipPostmortem: true }, member)) + .code, + ).toBe("permission_denied"); + expect( + ( + await rpcError( + "CloseIncident", + { id, skipPostmortem: true }, + keyWithoutCreator, + ) + ).code, + ).toBe("permission_denied"); + const closed = await rpc( + CloseIncidentResponseSchema, + "CloseIncident", + { id, skipPostmortem: true }, + admin, + ); + expect(closed.incident?.closedAt).toBeDefined(); + + const theirs = await resolvedIncident({ commanderEmail: memberEmail }); + const byCommander = await rpc( + CloseIncidentResponseSchema, + "CloseIncident", + { id: theirs, skipPostmortem: true }, + member, + ); + expect(byCommander.incident?.closedAt).toBeDefined(); + }); + + test("close without an approved postmortem needs skip_postmortem", async () => { + const id = await resolvedIncident(); + expect((await rpcError("CloseIncident", { id }, owner)).code).toBe( + "failed_precondition", + ); + }); + + test("close from open is failed_precondition", async () => { + const id = await declare(); + expect( + (await rpcError("CloseIncident", { id, skipPostmortem: true }, owner)) + .code, + ).toBe("failed_precondition"); + }); + + test("delete needs an owner or admin, even for the commander", async () => { + const id = await declare(owner, { commanderEmail: memberEmail }); + expect((await rpcError("DeleteIncident", { id }, member)).code).toBe( + "permission_denied", + ); + expect( + (await rpcError("DeleteIncident", { id }, keyWithoutCreator)).code, + ).toBe("permission_denied"); + const deleted = await rpc( + DeleteIncidentResponseSchema, + "DeleteIncident", + { id }, + admin, + ); + expect(deleted.success).toBe(true); + expect((await rpcError("GetIncident", { id }, owner)).code).toBe( + "not_found", + ); + }); + + test("delete after mitigation is failed_precondition", async () => { + const id = await declare(); + await setStatus(id, "INCIDENT_STATUS_MITIGATED"); + expect((await rpcError("DeleteIncident", { id }, admin)).code).toBe( + "failed_precondition", + ); + }); + + test("a key without a creator declares with no declarer", async () => { + const id = await declare(keyWithoutCreator); + expect((await get(id)).declaredBy).toBeUndefined(); + }); +}); + +describe("IncidentService: postmortem", () => { + test("is unset before anything is written", async () => { + const id = await declare(); + const res = await rpc( + GetPostmortemResponseSchema, + "GetPostmortem", + { incidentId: id }, + owner, + ); + expect(res.postmortem).toBeUndefined(); + }); + + test("can only be written once resolved", async () => { + const id = await declare(); + const err = await rpcError( + "UpdatePostmortem", + { incidentId: id, content: "too early" }, + owner, + ); + expect(err.code).toBe("failed_precondition"); + }); + + test("approval needs a role and keeps the postmortem approved on edit", async () => { + const id = await declare(); + await setStatus(id, "INCIDENT_STATUS_RESOLVED"); + await rpc( + UpdatePostmortemResponseSchema, + "UpdatePostmortem", + { incidentId: id, content: "draft" }, + owner, + ); + expect( + (await rpcError("ApprovePostmortem", { incidentId: id }, member)).code, + ).toBe("permission_denied"); + const approved = await rpc( + ApprovePostmortemResponseSchema, + "ApprovePostmortem", + { incidentId: id }, + admin, + ); + expect(approved.postmortem?.status).toBe(PostmortemStatus.APPROVED); + expect(approved.incident?.closedAt).toBeUndefined(); + + const edited = await rpc( + UpdatePostmortemResponseSchema, + "UpdatePostmortem", + { incidentId: id, content: "final" }, + owner, + ); + expect(edited.postmortem?.status).toBe(PostmortemStatus.APPROVED); + expect(edited.postmortem?.content).toBe("final"); + }); +}); + +describe("IncidentService: list", () => { + test("filters by status and closed, and counts", async () => { + const open = await declare(); + const resolved = await declare(); + await setStatus(resolved, "INCIDENT_STATUS_RESOLVED"); + const closed = await declare(); + await setStatus(closed, "INCIDENT_STATUS_RESOLVED"); + await rpc( + CloseIncidentResponseSchema, + "CloseIncident", + { id: closed, skipPostmortem: true }, + owner, + ); + + const all = await rpc( + ListIncidentsResponseSchema, + "ListIncidents", + { limit: 100 }, + owner, + ); + const ids = all.incidents.map((i) => i.id); + expect(ids.indexOf(open)).toBeLessThan(ids.indexOf(resolved)); + expect(all.totalSize).toBe(all.incidents.length); + + const page1 = await rpc( + ListIncidentsResponseSchema, + "ListIncidents", + { limit: 1 }, + owner, + ); + expect(page1.incidents).toHaveLength(1); + expect(page1.totalSize).toBe(all.totalSize); + + const openOnly = await rpc( + ListIncidentsResponseSchema, + "ListIncidents", + { statuses: ["INCIDENT_STATUS_OPEN"], limit: 100 }, + owner, + ); + expect( + openOnly.incidents.every((i) => i.status === IncidentStatus.OPEN), + ).toBe(true); + expect(openOnly.incidents.map((i) => i.id)).toContain(open); + + const needsPostmortem = await rpc( + ListIncidentsResponseSchema, + "ListIncidents", + { statuses: ["INCIDENT_STATUS_RESOLVED"], closed: false, limit: 100 }, + owner, + ); + const needsIds = needsPostmortem.incidents.map((i) => i.id); + expect(needsIds).toContain(resolved); + expect(needsIds).not.toContain(closed); + + const closedOnly = await rpc( + ListIncidentsResponseSchema, + "ListIncidents", + { closed: true, limit: 100 }, + owner, + ); + expect(closedOnly.incidents.map((i) => i.id)).toContain(closed); + expect(closedOnly.incidents.every((i) => i.closedAt !== undefined)).toBe( + true, + ); + }); + + test("rejects an out-of-range limit", async () => { + expect((await rpcError("ListIncidents", { limit: 500 }, owner)).code).toBe( + "invalid_argument", + ); + }); +}); + +describe("IncidentService: status report link", () => { + test("link and unlink", async () => { + const id = await declare(); + const report = await createReport(owner); + const reportId = report.statusReport?.id ?? ""; + const linked = await rpc( + LinkStatusReportResponseSchema, + "LinkStatusReport", + { id, statusReportId: reportId }, + owner, + ); + expect(linked.incident?.statusReport?.id).toBe(reportId); + expect(linked.incident?.statusReport?.pageId).toBe(String(pageId)); + + const fromReport = await rpc( + GetStatusReportResponseSchema, + "GetStatusReport", + { id: reportId }, + owner, + STATUS_REPORT, + ); + expect(fromReport.statusReport?.incidentId).toBe(id); + + const unlinked = await rpc( + UnlinkStatusReportResponseSchema, + "UnlinkStatusReport", + { id }, + owner, + ); + expect(unlinked.incident?.statusReport).toBeUndefined(); + }); + + test("CreateStatusReport links an incident in the same step", async () => { + const id = await declare(); + const report = await createReport(owner, id); + expect(report.statusReport?.incidentId).toBe(id); + expect((await get(id)).statusReport?.id).toBe(report.statusReport?.id); + + const list = await rpc( + ListStatusReportsResponseSchema, + "ListStatusReports", + { limit: 100 }, + owner, + STATUS_REPORT, + ); + const summary = list.statusReports.find( + (r) => r.id === report.statusReport?.id, + ); + expect(summary?.incidentId).toBe(id); + }); + + test("CreateStatusReport for a closed incident fails and creates nothing", async () => { + const id = await declare(); + await setStatus(id, "INCIDENT_STATUS_CANCELED"); + const before = await db + .select({ id: statusReport.id }) + .from(statusReport) + .where(eq(statusReport.workspaceId, workspaceId)) + .all(); + const err = await rpcError( + "CreateStatusReport", + { + title: `${TEST_PREFIX} report`, + status: "STATUS_REPORT_STATUS_INVESTIGATING", + message: "m", + date: new Date().toISOString(), + pageId: String(pageId), + incidentId: id, + }, + owner, + STATUS_REPORT, + ); + expect(err.code).toBe("failed_precondition"); + const after = await db + .select({ id: statusReport.id }) + .from(statusReport) + .where(eq(statusReport.workspaceId, workspaceId)) + .all(); + expect(after).toHaveLength(before.length); + }); + + test("a report already linked elsewhere is failed_precondition", async () => { + const first = await declare(); + const report = await createReport(owner, first); + const err = await rpcError( + "DeclareIncident", + { + title: "x", + severity: "INCIDENT_SEVERITY_MINOR", + statusReportId: report.statusReport?.id ?? "", + }, + owner, + ); + expect(err.code).toBe("failed_precondition"); + }); +}); + +describe("IncidentService: Slack", () => { + test("no channel unless open_slack_channel is set", async () => { + await declare(); + await settleBackgroundTasks(); + expect(slackCalls("conversations.create")).toHaveLength(0); + }); + + test("open_slack_channel creates and binds a channel", async () => { + const id = await declare(owner, { openSlackChannel: true }); + await settleBackgroundTasks(); + expect(slackCalls("conversations.create")).toHaveLength(1); + expect(slackCalls("pins.add")).toHaveLength(1); + expect((await get(id)).slackChannelUrl).toBe( + "https://slack.com/app_redirect?team=T_RPC&channel=C_INCIDENT", + ); + }); + + test("status changes announce in the bound channel and cancel archives it", async () => { + const id = await declare(); + await bindChannel(id); + await setStatus(id, "INCIDENT_STATUS_MITIGATED", owner, "rolled back"); + await settleBackgroundTasks(); + const posted = slackCalls("postMessage"); + expect(posted).toHaveLength(1); + expect(String(posted[0]?.args.text)).toContain( + "marked the incident *mitigated*", + ); + expect(slackCalls("conversations.archive")).toHaveLength(0); + + await setStatus(id, "INCIDENT_STATUS_CANCELED"); + await settleBackgroundTasks(); + expect(slackCalls("conversations.archive")).toHaveLength(1); + }); + + test("close and delete archive the bound channel", async () => { + const toClose = await declare(); + await bindChannel(toClose); + await setStatus(toClose, "INCIDENT_STATUS_RESOLVED"); + await rpc( + CloseIncidentResponseSchema, + "CloseIncident", + { id: toClose, skipPostmortem: true }, + owner, + ); + await settleBackgroundTasks(); + expect(slackCalls("conversations.archive")).toHaveLength(1); + + slackTestState.calls = []; + const toDelete = await declare(); + await db + .update(incident) + .set({ slackTeamId: "T_RPC", slackChannelId: "C_DELETE" }) + .where(eq(incident.id, Number(toDelete))); + await rpc( + DeleteIncidentResponseSchema, + "DeleteIncident", + { id: toDelete }, + owner, + ); + await settleBackgroundTasks(); + const archived = slackCalls("conversations.archive"); + expect(archived).toHaveLength(1); + expect(archived[0]?.args.channel).toBe("C_DELETE"); + }); +}); diff --git a/apps/server/src/routes/rpc/handlers/incident/converters.ts b/apps/server/src/routes/rpc/handlers/incident/converters.ts new file mode 100644 index 00000000..e95cfc0e --- /dev/null +++ b/apps/server/src/routes/rpc/handlers/incident/converters.ts @@ -0,0 +1,279 @@ +import type { + Incident, + IncidentEvent, + IncidentStatusReport, + IncidentSummary, + IncidentUser, + Postmortem, +} from "@openstatus/proto/incident/v1"; +import { + IncidentEventType, + IncidentSeverity, + IncidentStatus, + PostmortemAuthor, + PostmortemStatus, +} from "@openstatus/proto/incident/v1"; +import type { + getIncidentOrThrow, + getPostmortem, + listIncidentEvents, + listIncidents, +} from "@openstatus/services/incident"; +import { displayName } from "@openstatus/services/incident"; + +import { dbStatusToProto as dbReportStatusToProto } from "../status-report/converters"; +import { invalidEnumError } from "./errors"; + +type IncidentView = Awaited>; +type IncidentListItem = Awaited< + ReturnType +>["items"][number]; +type IncidentEventRow = Awaited>[number]; +type PostmortemRow = NonNullable>>; + +type DbSeverity = IncidentView["severity"]; +type DbStatus = IncidentView["status"]; +type DbEventType = IncidentEventRow["type"]; + +type UserRow = { + name: string | null; + firstName: string | null; + lastName: string | null; + email: string | null; + deletedAt: Date | null; +}; + +type IncidentFields = { + id: number; + title: string; + severity: DbSeverity; + status: DbStatus; + declaredAt: Date; + startedAt: Date; + resolvedAt: Date | null; + closedAt: Date | null; + createdAt: Date; + updatedAt: Date; + commander: UserRow | null; +}; + +export function protoSeverityToDb(severity: IncidentSeverity): DbSeverity { + switch (severity) { + case IncidentSeverity.CRITICAL: + return "critical"; + case IncidentSeverity.MAJOR: + return "major"; + case IncidentSeverity.MINOR: + return "minor"; + default: + throw invalidEnumError("severity"); + } +} + +export function dbSeverityToProto(severity: DbSeverity): IncidentSeverity { + switch (severity) { + case "critical": + return IncidentSeverity.CRITICAL; + case "major": + return IncidentSeverity.MAJOR; + case "minor": + return IncidentSeverity.MINOR; + } +} + +export function protoStatusToDb(status: IncidentStatus): DbStatus { + switch (status) { + case IncidentStatus.OPEN: + return "open"; + case IncidentStatus.MITIGATED: + return "mitigated"; + case IncidentStatus.RESOLVED: + return "resolved"; + case IncidentStatus.CANCELED: + return "canceled"; + default: + throw invalidEnumError("status"); + } +} + +export function dbStatusToProto(status: DbStatus): IncidentStatus { + switch (status) { + case "open": + return IncidentStatus.OPEN; + case "mitigated": + return IncidentStatus.MITIGATED; + case "resolved": + return IncidentStatus.RESOLVED; + case "canceled": + return IncidentStatus.CANCELED; + } +} + +export function dbEventTypeToProto(type: DbEventType): IncidentEventType { + switch (type) { + case "declared": + return IncidentEventType.DECLARED; + case "severity_changed": + return IncidentEventType.SEVERITY_CHANGED; + // `mitigated` is in the enum but never written: mitigation is a status change. + case "status_changed": + case "mitigated": + return IncidentEventType.STATUS_CHANGED; + case "commander_changed": + return IncidentEventType.COMMANDER_CHANGED; + case "started_at_changed": + return IncidentEventType.STARTED_AT_CHANGED; + case "note": + return IncidentEventType.NOTE; + case "status_report_linked": + return IncidentEventType.STATUS_REPORT_LINKED; + case "status_report_unlinked": + return IncidentEventType.STATUS_REPORT_UNLINKED; + case "slack_channel_bound": + return IncidentEventType.SLACK_CHANNEL_BOUND; + case "slack_channel_unbound": + return IncidentEventType.SLACK_CHANNEL_UNBOUND; + case "resolved": + return IncidentEventType.RESOLVED; + case "canceled": + return IncidentEventType.CANCELED; + case "postmortem_drafted": + return IncidentEventType.POSTMORTEM_DRAFTED; + case "postmortem_updated": + return IncidentEventType.POSTMORTEM_UPDATED; + case "postmortem_approved": + return IncidentEventType.POSTMORTEM_APPROVED; + case "closed": + return IncidentEventType.CLOSED; + } +} + +export function toIncidentUser( + row: UserRow | null | undefined, +): IncidentUser | undefined { + if (!row) return undefined; + if (row.deletedAt) { + return { + $typeName: "openstatus.incident.v1.IncidentUser", + email: "", + name: "Deleted user", + }; + } + return { + $typeName: "openstatus.incident.v1.IncidentUser", + email: row.email ?? "", + name: displayName(row), + }; +} + +export function toSlackChannelUrl( + teamId: string | null, + channelId: string | null, +): string | undefined { + if (!teamId || !channelId) return undefined; + const params = new URLSearchParams({ team: teamId, channel: channelId }); + return `https://slack.com/app_redirect?${params.toString()}`; +} + +function toStatusReport( + report: + | { + id: number; + title: string; + status: Parameters[0]; + pageId?: number | null; + } + | null + | undefined, +): IncidentStatusReport | undefined { + if (!report) return undefined; + return { + $typeName: "openstatus.incident.v1.IncidentStatusReport", + id: String(report.id), + title: report.title, + status: dbReportStatusToProto(report.status), + pageId: report.pageId == null ? "" : String(report.pageId), + }; +} + +function iso(date: Date | null | undefined): string | undefined { + return date ? date.toISOString() : undefined; +} + +function summaryFields(row: IncidentFields) { + return { + id: String(row.id), + title: row.title, + severity: dbSeverityToProto(row.severity), + status: dbStatusToProto(row.status), + commander: toIncidentUser(row.commander), + declaredAt: row.declaredAt.toISOString(), + startedAt: row.startedAt.toISOString(), + resolvedAt: iso(row.resolvedAt), + closedAt: iso(row.closedAt), + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + }; +} + +export function incidentSummaryToProto(row: IncidentListItem): IncidentSummary { + return { + $typeName: "openstatus.incident.v1.IncidentSummary", + ...summaryFields(row), + statusReport: toStatusReport(row.statusReport), + }; +} + +export function incidentEventToProto( + event: Pick & { + createdByUser: UserRow | null; + }, +): IncidentEvent { + return { + $typeName: "openstatus.incident.v1.IncidentEvent", + id: String(event.id), + type: dbEventTypeToProto(event.type), + message: event.message ?? "", + createdBy: toIncidentUser(event.createdByUser), + createdAt: event.createdAt.toISOString(), + }; +} + +export function incidentToProto( + view: IncidentView, + events: IncidentEventRow[] = [], +): Incident { + return { + $typeName: "openstatus.incident.v1.Incident", + ...summaryFields(view), + summary: view.summary ?? undefined, + declaredBy: toIncidentUser(view.declaredByUser), + resolvedBy: toIncidentUser(view.resolvedByUser), + mitigatedAt: iso(view.mitigatedAt), + statusReport: toStatusReport(view.statusReport), + slackChannelUrl: toSlackChannelUrl(view.slackTeamId, view.slackChannelId), + allowedTransitions: view.allowedTransitions.map(dbStatusToProto), + deletable: view.deletable, + events: events.map(incidentEventToProto), + }; +} + +export function postmortemToProto(row: PostmortemRow): Postmortem { + return { + $typeName: "openstatus.incident.v1.Postmortem", + incidentId: String(row.incidentId), + status: + row.status === "approved" + ? PostmortemStatus.APPROVED + : PostmortemStatus.DRAFT, + content: row.content, + draftedBy: + row.draftedBy === "agent" + ? PostmortemAuthor.AGENT + : PostmortemAuthor.USER, + approvedBy: toIncidentUser(row.approvedByUser), + approvedAt: iso(row.approvedAt), + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + }; +} diff --git a/apps/server/src/routes/rpc/handlers/incident/errors.ts b/apps/server/src/routes/rpc/handlers/incident/errors.ts new file mode 100644 index 00000000..953cf7b9 --- /dev/null +++ b/apps/server/src/routes/rpc/handlers/incident/errors.ts @@ -0,0 +1,86 @@ +import { Code, ConnectError } from "@connectrpc/connect"; + +export const ErrorReason = { + INCIDENT_NOT_FOUND: "INCIDENT_NOT_FOUND", + INVALID_ID: "INVALID_ID", + INVALID_COMMANDER: "INVALID_COMMANDER", + CONFLICTING_FIELDS: "CONFLICTING_FIELDS", + INVALID_DATE_FORMAT: "INVALID_DATE_FORMAT", + INVALID_ENUM: "INVALID_ENUM", +} as const; + +export type ErrorReason = (typeof ErrorReason)[keyof typeof ErrorReason]; + +const DOMAIN = "openstatus.dev"; + +function createError( + message: string, + code: Code, + reason: ErrorReason, + metadata?: Record, +): ConnectError { + const headers = new Headers({ + "error-domain": DOMAIN, + "error-reason": reason, + }); + if (metadata) { + for (const [key, value] of Object.entries(metadata)) { + headers.set(`error-${key}`, value); + } + } + return new ConnectError(message, code, headers); +} + +export function incidentNotFoundError(incidentId: string): ConnectError { + return createError( + "Incident not found", + Code.NotFound, + ErrorReason.INCIDENT_NOT_FOUND, + { "incident-id": incidentId }, + ); +} + +export function invalidIdError(field: string, value: string): ConnectError { + return createError( + `Invalid ${field}: "${value}"`, + Code.InvalidArgument, + ErrorReason.INVALID_ID, + { field }, + ); +} + +export function invalidCommanderError(email: string): ConnectError { + return createError( + `No workspace member with email "${email}"`, + Code.InvalidArgument, + ErrorReason.INVALID_COMMANDER, + ); +} + +export function conflictingFieldsError( + value: string, + clear: string, +): ConnectError { + return createError( + `Set either ${value} or ${clear}, not both`, + Code.InvalidArgument, + ErrorReason.CONFLICTING_FIELDS, + ); +} + +export function invalidDateFormatError(value: string): ConnectError { + return createError( + `Invalid date format: "${value}". Expected RFC 3339.`, + Code.InvalidArgument, + ErrorReason.INVALID_DATE_FORMAT, + ); +} + +export function invalidEnumError(field: string): ConnectError { + return createError( + `Invalid ${field}`, + Code.InvalidArgument, + ErrorReason.INVALID_ENUM, + { field }, + ); +} diff --git a/apps/server/src/routes/rpc/handlers/incident/index.ts b/apps/server/src/routes/rpc/handlers/incident/index.ts new file mode 100644 index 00000000..7b64fb7b --- /dev/null +++ b/apps/server/src/routes/rpc/handlers/incident/index.ts @@ -0,0 +1,365 @@ +import type { ServiceImpl } from "@connectrpc/connect"; +import { sendIncidentCommander } from "@openstatus/emails"; +import type { IncidentService } from "@openstatus/proto/incident/v1"; +import type { ServiceContext } from "@openstatus/services"; +import { + actorDisplayName, + addIncidentNote, + afterIncidentClosed, + afterIncidentDeclared, + afterIncidentDeleted, + afterIncidentStatusChanged, + afterIncidentUpdated, + afterPostmortemApproved, + approvePostmortem, + closeIncident, + declareIncident, + deleteIncident, + draftPostmortem, + escapeMrkdwn, + getIncidentOrThrow, + getPostmortem, + type IncidentEffects, + linkIncidentStatusReport, + listIncidentEvents, + listIncidents, + resolveDashboardUrl, + setIncidentStatus, + unlinkIncidentStatusReport, + updateIncident, +} from "@openstatus/services/incident"; +import { findMemberIdByEmail } from "@openstatus/services/member"; +import { WebClient } from "@slack/web-api"; + +import { env } from "@/env"; +import { runInBackground } from "@/libs/background"; + +import { toConnectError, toServiceCtx } from "../../adapter"; +import { getRpcContext } from "../../interceptors"; +import { + incidentEventToProto, + incidentSummaryToProto, + incidentToProto, + postmortemToProto, + protoSeverityToDb, + protoStatusToDb, +} from "./converters"; +import { + conflictingFieldsError, + invalidCommanderError, + invalidDateFormatError, + invalidIdError, +} from "./errors"; + +const DASHBOARD_URL = resolveDashboardUrl({ + nodeEnv: env.NODE_ENV, + override: env.DASHBOARD_URL, +}); + +const NUMERIC_ID = /^\d+$/; + +function parseId(field: string, value: string): number { + const trimmed = value.trim(); + if (!NUMERIC_ID.test(trimmed)) throw invalidIdError(field, value); + return Number(trimmed); +} + +function parseDate(value: string): Date { + const date = new Date(value); + if (Number.isNaN(date.getTime())) throw invalidDateFormatError(value); + return date; +} + +async function resolveCommander( + ctx: ServiceContext, + email: string, +): Promise { + const id = await findMemberIdByEmail({ ctx, input: { email } }); + if (id === null) throw invalidCommanderError(email); + return id; +} + +async function effectsFor(ctx: ServiceContext): Promise { + const name = (await actorDisplayName(ctx)) ?? "An API key"; + return { + clientFor: (token) => new WebClient(token), + dashboardUrl: DASHBOARD_URL, + sendCommanderEmail: sendIncidentCommander, + actorLabel: escapeMrkdwn(name), + assignedBy: name, + }; +} + +function afterResponse( + ctx: ServiceContext, + label: string, + incidentId: number, + run: (effects: IncidentEffects) => Promise, +): void { + runInBackground( + `incident ${label}`, + async () => { + await run(await effectsFor(ctx)); + }, + { incidentId }, + ); +} + +async function readIncident(ctx: ServiceContext, id: number) { + return incidentToProto(await getIncidentOrThrow({ ctx, input: { id } })); +} + +export const incidentServiceImpl: ServiceImpl = { + async declareIncident(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const row = await declareIncident({ + ctx: sCtx, + input: { + title: req.title, + severity: protoSeverityToDb(req.severity), + summary: req.summary, + commanderId: + req.commanderEmail === undefined + ? null + : await resolveCommander(sCtx, req.commanderEmail), + startedAt: + req.startedAt === undefined ? undefined : parseDate(req.startedAt), + statusReportId: + req.statusReportId === undefined + ? undefined + : parseId("status_report_id", req.statusReportId), + }, + }); + afterResponse(sCtx, "declare", row.id, (effects) => + afterIncidentDeclared({ + ctx: sCtx, + effects, + incident: row, + openSlackChannel: req.openSlackChannel ?? false, + }), + ); + return { incident: await readIncident(sCtx, row.id) }; + } catch (err) { + toConnectError(err); + } + }, + + async getIncident(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + const view = await getIncidentOrThrow({ ctx: sCtx, input: { id } }); + const events = await listIncidentEvents({ ctx: sCtx, input: { id } }); + return { incident: incidentToProto(view, events) }; + } catch (err) { + toConnectError(err); + } + }, + + async listIncidents(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const { items, totalSize } = await listIncidents({ + ctx: sCtx, + input: { + status: req.statuses.map(protoStatusToDb), + closed: req.closed, + limit: Math.min(Math.max(req.limit ?? 50, 1), 100), + offset: req.offset ?? 0, + }, + }); + return { incidents: items.map(incidentSummaryToProto), totalSize }; + } catch (err) { + toConnectError(err); + } + }, + + async updateIncident(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + if (req.summary !== undefined && req.clearSummary) { + throw conflictingFieldsError("summary", "clear_summary"); + } + if (req.commanderEmail !== undefined && req.clearCommander) { + throw conflictingFieldsError("commander_email", "clear_commander"); + } + const before = await getIncidentOrThrow({ ctx: sCtx, input: { id } }); + const after = await updateIncident({ + ctx: sCtx, + input: { + id, + title: req.title, + severity: + req.severity === undefined + ? undefined + : protoSeverityToDb(req.severity), + summary: req.clearSummary ? null : req.summary, + commanderId: req.clearCommander + ? null + : req.commanderEmail === undefined + ? undefined + : await resolveCommander(sCtx, req.commanderEmail), + startedAt: + req.startedAt === undefined ? undefined : parseDate(req.startedAt), + }, + }); + afterResponse(sCtx, "update", id, (effects) => + afterIncidentUpdated({ ctx: sCtx, effects, before, after }), + ); + return { incident: await readIncident(sCtx, id) }; + } catch (err) { + toConnectError(err); + } + }, + + async setIncidentStatus(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + const note = req.note?.trim() || undefined; + const row = await setIncidentStatus({ + ctx: sCtx, + input: { id, status: protoStatusToDb(req.status), note }, + }); + afterResponse(sCtx, "status", id, (effects) => + afterIncidentStatusChanged({ + ctx: sCtx, + effects, + incidentId: id, + status: row.status, + note, + }), + ); + return { incident: await readIncident(sCtx, id) }; + } catch (err) { + toConnectError(err); + } + }, + + async addIncidentNote(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + const added = await addIncidentNote({ + ctx: sCtx, + input: { id, message: req.message }, + }); + const events = await listIncidentEvents({ ctx: sCtx, input: { id } }); + const event = events.find((e) => e.id === added.id); + return { + event: incidentEventToProto(event ?? { ...added, createdByUser: null }), + }; + } catch (err) { + toConnectError(err); + } + }, + + async linkStatusReport(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + await linkIncidentStatusReport({ + ctx: sCtx, + input: { + id, + statusReportId: parseId("status_report_id", req.statusReportId), + }, + }); + return { incident: await readIncident(sCtx, id) }; + } catch (err) { + toConnectError(err); + } + }, + + async unlinkStatusReport(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + await unlinkIncidentStatusReport({ ctx: sCtx, input: { id } }); + return { incident: await readIncident(sCtx, id) }; + } catch (err) { + toConnectError(err); + } + }, + + async closeIncident(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + await closeIncident({ + ctx: sCtx, + input: { id, skipPostmortem: req.skipPostmortem }, + }); + afterResponse(sCtx, "close", id, (effects) => + afterIncidentClosed({ ctx: sCtx, effects, incidentId: id }), + ); + return { incident: await readIncident(sCtx, id) }; + } catch (err) { + toConnectError(err); + } + }, + + async deleteIncident(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("id", req.id); + const before = await getIncidentOrThrow({ ctx: sCtx, input: { id } }); + await deleteIncident({ ctx: sCtx, input: { id } }); + afterResponse(sCtx, "delete", id, (effects) => + afterIncidentDeleted({ ctx: sCtx, effects, before }), + ); + return { success: true }; + } catch (err) { + toConnectError(err); + } + }, + + async getPostmortem(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("incident_id", req.incidentId); + const row = await getPostmortem({ ctx: sCtx, input: { id } }); + return { postmortem: row ? postmortemToProto(row) : undefined }; + } catch (err) { + toConnectError(err); + } + }, + + async updatePostmortem(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("incident_id", req.incidentId); + await draftPostmortem({ + ctx: sCtx, + input: { id, content: req.content, draftedBy: "user" }, + }); + const row = await getPostmortem({ ctx: sCtx, input: { id } }); + return { postmortem: row ? postmortemToProto(row) : undefined }; + } catch (err) { + toConnectError(err); + } + }, + + async approvePostmortem(req, ctx) { + try { + const sCtx = toServiceCtx(getRpcContext(ctx)); + const id = parseId("incident_id", req.incidentId); + const close = req.close ?? false; + const before = await getIncidentOrThrow({ ctx: sCtx, input: { id } }); + await approvePostmortem({ ctx: sCtx, input: { id, close } }); + const closed = close && !before.closedAt; + afterResponse(sCtx, "approve postmortem", id, (effects) => + afterPostmortemApproved({ ctx: sCtx, effects, incidentId: id, closed }), + ); + const row = await getPostmortem({ ctx: sCtx, input: { id } }); + return { + postmortem: row ? postmortemToProto(row) : undefined, + incident: await readIncident(sCtx, id), + }; + } catch (err) { + toConnectError(err); + } + }, +}; diff --git a/apps/server/src/routes/rpc/handlers/status-report/converters.ts b/apps/server/src/routes/rpc/handlers/status-report/converters.ts index 0d6b444c..ab198a77 100644 --- a/apps/server/src/routes/rpc/handlers/status-report/converters.ts +++ b/apps/server/src/routes/rpc/handlers/status-report/converters.ts @@ -31,6 +31,7 @@ type DBStatusReport = { pageId: number | null; createdAt: Date | null; updatedAt: Date | null; + incidentId?: number | null; }; type DBStatusReportUpdate = { @@ -173,6 +174,7 @@ export function dbReportToProtoSummary( pageComponentIds, createdAt: report.createdAt?.toISOString() ?? "", updatedAt: report.updatedAt?.toISOString() ?? "", + incidentId: optionalId(report.incidentId), }; } @@ -193,5 +195,10 @@ export function dbReportToProto( updates: updates.map(dbUpdateToProto), createdAt: report.createdAt?.toISOString() ?? "", updatedAt: report.updatedAt?.toISOString() ?? "", + incidentId: optionalId(report.incidentId), }; } + +function optionalId(id: number | null | undefined): string | undefined { + return id == null ? undefined : String(id); +} diff --git a/apps/server/src/routes/rpc/handlers/status-report/index.ts b/apps/server/src/routes/rpc/handlers/status-report/index.ts index e50d07dd..05144106 100644 --- a/apps/server/src/routes/rpc/handlers/status-report/index.ts +++ b/apps/server/src/routes/rpc/handlers/status-report/index.ts @@ -35,12 +35,12 @@ function parseDate(dateString: string): Date { // Match the digits explicitly: `Number("")` is 0 (finite!), so a blank id used // to slip through and target component 0, and `Number.parseInt("1.5")` is 1, so // swapping in parseInt alone would still truncate a malformed id silently. -const PAGE_COMPONENT_ID = /^\d+$/; +const NUMERIC_ID = /^\d+$/; function parsePageComponentIds(ids: ReadonlyArray): number[] { return ids.map((id) => { const trimmed = id.trim(); - if (!PAGE_COMPONENT_ID.test(trimmed)) { + if (!NUMERIC_ID.test(trimmed)) { throw new ConnectError( `Invalid page component id: "${id}"`, Code.InvalidArgument, @@ -50,6 +50,17 @@ function parsePageComponentIds(ids: ReadonlyArray): number[] { }); } +function parseIncidentId(id: string): number { + const trimmed = id.trim(); + if (!NUMERIC_ID.test(trimmed)) { + throw new ConnectError( + `Invalid incident id: "${id}"`, + Code.InvalidArgument, + ); + } + return Number(trimmed); +} + // empty list ⇒ undefined: an old client omitting the field must produce a // legacy report (zero impact rows), never default to operational function parseComponentImpacts( @@ -86,6 +97,10 @@ export const statusReportServiceImpl: ServiceImpl = pageId, pageComponentIds: parsePageComponentIds(req.pageComponentIds), componentImpacts: parseComponentImpacts(req.componentImpacts), + incidentId: + req.incidentId === undefined + ? undefined + : parseIncidentId(req.incidentId), }, }); diff --git a/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts b/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts index e9bc2068..fdec7cad 100644 --- a/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts +++ b/apps/server/src/routes/rpc/interceptors/__tests__/tracking.test.ts @@ -1,5 +1,13 @@ +import { create } from "@bufbuild/protobuf"; import type { Interceptor } from "@connectrpc/connect"; import { Events } from "@openstatus/analytics"; +import { + DeclareIncidentRequestSchema, + IncidentService, + IncidentSeverity, + IncidentStatus, + SetIncidentStatusRequestSchema, +} from "@openstatus/proto/incident/v1"; import { MonitorService } from "@openstatus/proto/monitor/v1"; // @ts-nocheck — ConnectRPC's deep generic types (AnyFn, UnaryResponse, etc.) // are incompatible with the test mocks. All runtime behavior is correct. @@ -304,3 +312,57 @@ describe("RPC_EVENT_MAP", () => { ); }); }); + +describe("IncidentService tracking", () => { + beforeEach(() => { + mockSetupAnalytics.mockClear(); + mockTrack.mockClear(); + }); + + test("every mutating IncidentService RPC except unlink is tracked", () => { + const mutating = Object.values(IncidentService.method) + .map((m) => m.name) + .filter((name) => !/^(Get|List)/.test(name)); + const untracked = mutating.filter( + (name) => + !(`openstatus.incident.v1.IncidentService/${name}` in RPC_EVENT_MAP), + ); + expect(untracked).toEqual(["UnlinkStatusReport"]); + }); + + test("declare carries the source and a readable severity", async () => { + const interceptor = trackingInterceptor(); + const req = createMockRequest( + "openstatus.incident.v1.IncidentService", + "DeclareIncident", + create(DeclareIncidentRequestSchema, { + title: "API down", + severity: IncidentSeverity.CRITICAL, + }), + ); + await interceptor(mockNext({}))(req as never); + await Promise.resolve(); + expect(mockTrack).toHaveBeenCalledWith({ + ...Events.DeclareManagedIncident, + additionalProps: { source: "api", severity: "critical" }, + }); + }); + + test("a status change carries the target status", async () => { + const interceptor = trackingInterceptor(); + const req = createMockRequest( + "openstatus.incident.v1.IncidentService", + "SetIncidentStatus", + create(SetIncidentStatusRequestSchema, { + id: "1", + status: IncidentStatus.RESOLVED, + }), + ); + await interceptor(mockNext({}))(req as never); + await Promise.resolve(); + expect(mockTrack).toHaveBeenCalledWith({ + ...Events.ChangeManagedIncidentStatus, + additionalProps: { source: "api", status: "resolved" }, + }); + }); +}); diff --git a/apps/server/src/routes/rpc/interceptors/tracking.ts b/apps/server/src/routes/rpc/interceptors/tracking.ts index 57cdae60..62d3d0f2 100644 --- a/apps/server/src/routes/rpc/interceptors/tracking.ts +++ b/apps/server/src/routes/rpc/interceptors/tracking.ts @@ -1,3 +1,4 @@ +import { isMessage, type Message } from "@bufbuild/protobuf"; import type { Interceptor } from "@connectrpc/connect"; import { getLogger } from "@logtape/logtape"; import { @@ -6,6 +7,12 @@ import { parseInputToProps, setupAnalytics, } from "@openstatus/analytics"; +import { + DeclareIncidentRequestSchema, + IncidentSeverity, + IncidentStatus, + SetIncidentStatusRequestSchema, +} from "@openstatus/proto/incident/v1"; import { RPC_CONTEXT_KEY } from "./auth"; @@ -15,8 +22,25 @@ type RpcEventMapping = { event: EventProps; eventProps?: string[]; normalizeInput?: (message: unknown) => Record; + props?: (message: Message) => Record; }; +function incidentProps(message: Message): Record { + if (isMessage(message, DeclareIncidentRequestSchema)) { + return { + source: "api", + severity: IncidentSeverity[message.severity].toLowerCase(), + }; + } + if (isMessage(message, SetIncidentStatusRequestSchema)) { + return { + source: "api", + status: IncidentStatus[message.status].toLowerCase(), + }; + } + return { source: "api" }; +} + // Create*Monitor requests nest the config under `monitor`, so top-level // extraction yields nothing; ICMP and gRPC name their target `uri` and none of // them carries jobType on the wire. @@ -147,6 +171,44 @@ export const RPC_EVENT_MAP: Record = { { event: Events.DeletePrivateLocation, }, + + // IncidentService + "openstatus.incident.v1.IncidentService/DeclareIncident": { + event: Events.DeclareManagedIncident, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/UpdateIncident": { + event: Events.UpdateManagedIncident, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/SetIncidentStatus": { + event: Events.ChangeManagedIncidentStatus, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/AddIncidentNote": { + event: Events.AddManagedIncidentNote, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/LinkStatusReport": { + event: Events.LinkManagedIncidentReport, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/CloseIncident": { + event: Events.CloseManagedIncident, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/DeleteIncident": { + event: Events.DeleteManagedIncident, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/UpdatePostmortem": { + event: Events.DraftManagedPostmortem, + props: incidentProps, + }, + "openstatus.incident.v1.IncidentService/ApprovePostmortem": { + event: Events.ApproveManagedPostmortem, + props: incidentProps, + }, }; /** @@ -173,7 +235,10 @@ export function trackingInterceptor(): Interceptor { } const input = mapping.normalizeInput?.(req.message) ?? req.message; - const additionalProps = parseInputToProps(input, mapping.eventProps); + const additionalProps = { + ...parseInputToProps(input, mapping.eventProps), + ...(mapping.props && !req.stream ? mapping.props(req.message) : {}), + }; setupAnalytics({ userId: `api_${rpcCtx.workspace.id}`, diff --git a/apps/server/src/routes/rpc/router.ts b/apps/server/src/routes/rpc/router.ts index 6233edaa..845d5313 100644 --- a/apps/server/src/routes/rpc/router.ts +++ b/apps/server/src/routes/rpc/router.ts @@ -1,5 +1,6 @@ import { createConnectRouter } from "@connectrpc/connect"; import { HealthService } from "@openstatus/proto/health/v1"; +import { IncidentService } from "@openstatus/proto/incident/v1"; import { MaintenanceService } from "@openstatus/proto/maintenance/v1"; import { MonitorService } from "@openstatus/proto/monitor/v1"; import { NotificationService } from "@openstatus/proto/notification/v1"; @@ -8,6 +9,7 @@ import { StatusPageService } from "@openstatus/proto/status_page/v1"; import { StatusReportService } from "@openstatus/proto/status_report/v1"; import { healthServiceImpl } from "./handlers/health"; +import { incidentServiceImpl } from "./handlers/incident"; import { maintenanceServiceImpl } from "./handlers/maintenance"; import { monitorServiceImpl } from "./handlers/monitor"; import { notificationServiceImpl } from "./handlers/notification"; @@ -46,4 +48,5 @@ export const routes = createConnectRouter({ .service(StatusPageService, statusPageServiceImpl) .service(MaintenanceService, maintenanceServiceImpl) .service(NotificationService, notificationServiceImpl) - .service(PrivateLocationService, privateLocationServiceImpl); + .service(PrivateLocationService, privateLocationServiceImpl) + .service(IncidentService, incidentServiceImpl); diff --git a/apps/server/src/routes/slack/commands.ts b/apps/server/src/routes/slack/commands.ts index 012bea8b..eaef5aa1 100644 --- a/apps/server/src/routes/slack/commands.ts +++ b/apps/server/src/routes/slack/commands.ts @@ -9,7 +9,8 @@ import { WebClient } from "@slack/web-api"; import type { Context } from "hono"; import { z } from "zod"; -import { runInBackground } from "./background"; +import { runInBackground } from "@/libs/background"; + import { type Block, buildLinkAccountBlocks, diff --git a/apps/server/src/routes/slack/config.ts b/apps/server/src/routes/slack/config.ts index 39fa615a..4ab39e55 100644 --- a/apps/server/src/routes/slack/config.ts +++ b/apps/server/src/routes/slack/config.ts @@ -1,3 +1,5 @@ +import { resolveDashboardUrl } from "@openstatus/services/incident"; + import { env } from "@/env"; /** @@ -33,9 +35,9 @@ export function slackConfigFromEnv(): SlackConfig { clientSecret: env.SLACK_CLIENT_SECRET, redirectUri: env.SLACK_REDIRECT_URI, aiGatewayApiKey: env.AI_GATEWAY_API_KEY, - dashboardUrl: - env.NODE_ENV === "production" - ? "https://app.openstatus.dev" - : "http://localhost:3001", + dashboardUrl: resolveDashboardUrl({ + nodeEnv: env.NODE_ENV, + override: env.DASHBOARD_URL, + }), }; } diff --git a/apps/server/src/routes/slack/handler.test.ts b/apps/server/src/routes/slack/handler.test.ts index 42720c7d..38a58072 100644 --- a/apps/server/src/routes/slack/handler.test.ts +++ b/apps/server/src/routes/slack/handler.test.ts @@ -23,6 +23,7 @@ import { } from "@openstatus/test-utils"; import { Hono } from "hono"; +import { settleBackgroundTasks } from "@/libs/background"; // workspace-resolver / @slack/web-api / agent are swapped for doubles via the // test import map; behavior is driven through this shared mutable state. import { slackTestState } from "@/libs/test/doubles/slack-test-state"; @@ -31,7 +32,6 @@ import { withSlackConfig, } from "@/libs/test/slack-config"; -import { settleBackgroundTasks } from "./background"; import type { SlackEnv } from "./config"; import { handleSlackEvent, diff --git a/apps/server/src/routes/slack/handler.ts b/apps/server/src/routes/slack/handler.ts index 0e989cea..fd91d132 100644 --- a/apps/server/src/routes/slack/handler.ts +++ b/apps/server/src/routes/slack/handler.ts @@ -9,11 +9,11 @@ import { WebClient } from "@slack/web-api"; import type { Context } from "hono"; import { z } from "zod"; +import { runInBackground } from "@/libs/background"; import { redis } from "@/libs/clients"; import { type AgentEvents, runAgent } from "./agent"; import { greetOnce, setAssistantStatus, setSessionStatus } from "./assistant"; -import { runInBackground } from "./background"; import { type Block, buildAnswerMessage, diff --git a/apps/server/src/routes/slack/home.ts b/apps/server/src/routes/slack/home.ts index be7fa2f8..385e3361 100644 --- a/apps/server/src/routes/slack/home.ts +++ b/apps/server/src/routes/slack/home.ts @@ -85,7 +85,7 @@ export async function homeIncidents( ctx: ServiceContext, ): Promise { if (!isFeatureEnabled(ctx.workspace, "incident-management")) return; - const rows = await listIncidents({ + const { items: rows } = await listIncidents({ ctx, input: { status: ["open", "mitigated"], limit: 10 }, }); diff --git a/apps/server/src/routes/slack/incident-commands.ts b/apps/server/src/routes/slack/incident-commands.ts index 67b6a3d9..e77dbf3a 100644 --- a/apps/server/src/routes/slack/incident-commands.ts +++ b/apps/server/src/routes/slack/incident-commands.ts @@ -215,7 +215,7 @@ export async function runIncidentCommand(args: { return `*${target.title}* · ${target.severity} · ${target.status}${target.closedAt ? " (closed)" : ""}\nCommander: ${commander}${target.statusReport ? `\nStatus report: ${target.statusReport.title} (${target.statusReport.status})` : ""}\n<${getIncidentDashboardUrl(target.id)}|Open in openstatus>`; } case "list": { - const open = await listIncidents({ + const { items: open } = await listIncidents({ ctx, input: { status: ["open", "mitigated"], limit: 20 }, }); diff --git a/apps/server/src/routes/slack/incident-modal.ts b/apps/server/src/routes/slack/incident-modal.ts index 72992153..4de49559 100644 --- a/apps/server/src/routes/slack/incident-modal.ts +++ b/apps/server/src/routes/slack/incident-modal.ts @@ -8,7 +8,8 @@ import { escapeMrkdwn } from "@openstatus/services/incident"; import { type ModalView, WebClient } from "@slack/web-api"; import { z } from "zod"; -import { runInBackground } from "./background"; +import { runInBackground } from "@/libs/background"; + import { type Block, buildLinkAccountBlocks, diff --git a/apps/server/src/routes/slack/incident-slack.ts b/apps/server/src/routes/slack/incident-slack.ts index e220979f..aa70a217 100644 --- a/apps/server/src/routes/slack/incident-slack.ts +++ b/apps/server/src/routes/slack/incident-slack.ts @@ -1,14 +1,17 @@ import { getLogger } from "@logtape/logtape"; +import { incidentStatus } from "@openstatus/db/src/schema/incidents/constants"; import { sendIncidentCommander } from "@openstatus/emails"; import { ServiceError, type ServiceContext } from "@openstatus/services"; import { + actorDisplayName, + afterIncidentDeclared, + afterIncidentStatusChanged, + afterPostmortemApproved, announceIncidentChange, bindIncidentSlackChannel, - displayName, - escapeMrkdwn, getIncident, + type IncidentEffects, type OpenChannelResult, - openIncidentSlackChannel, type SlackClientFactory, } from "@openstatus/services/incident"; import { WebClient } from "@slack/web-api"; @@ -27,15 +30,28 @@ export const slackClientFor: SlackClientFactory = (token) => export const INCIDENT_BIND_ACTION_PREFIX = "incident_bind_"; -const incidentOutput = z.object({ id: z.number().int(), status: z.string() }); +const incidentStatusSchema = z.enum(incidentStatus); +const incidentOutput = z.object({ + id: z.number().int(), + status: incidentStatusSchema, +}); const incidentInput = z.object({ note: z.string().optional() }); function who(ctx: ServiceContext): string { return ctx.actor.type === "slack" ? `<@${ctx.actor.slackUserId}>` : "Someone"; } -function quote(note: string | undefined): string { - return note ? `\n>${escapeMrkdwn(note).replaceAll("\n", "\n>")}` : ""; +async function slackEffects( + ctx: ServiceContext, + config: SlackConfig, +): Promise { + return { + clientFor: slackClientFor, + dashboardUrl: config.dashboardUrl, + sendCommanderEmail: sendIncidentCommander, + actorLabel: who(ctx), + assignedBy: (await actorDisplayName(ctx)) ?? "A teammate", + }; } const INCIDENT_TOOLS = new Set([ @@ -70,15 +86,11 @@ export async function afterIncidentTool(args: { const close = out.data.closed === true; trackSlackIncident(ctx, "approved"); if (close) trackSlackIncident(ctx, "closed"); - await announceIncidentChange({ + await afterPostmortemApproved({ ctx, + effects: await slackEffects(ctx, config), incidentId: out.data.incidentId, - text: close - ? `${who(ctx)} approved the postmortem and closed the incident.` - : `${who(ctx)} approved the postmortem.`, - clientFor: slackClientFor, - dashboardUrl: config.dashboardUrl, - archive: close, + closed: close, }).catch(() => undefined); return; } @@ -120,12 +132,12 @@ export async function afterIncidentTool(args: { // An archived channel can't take the resolve card, so keep it open. const cardInIncidentChannel = !!report && row?.slackChannelId === args.channelId; - await announceIncidentChange({ + await afterIncidentStatusChanged({ ctx, + effects: await slackEffects(ctx, config), incidentId, - text: `${who(ctx)} marked the incident *${status}*.${quote(note)}`, - clientFor: slackClientFor, - dashboardUrl: config.dashboardUrl, + status, + note, archive: status === "canceled" && !cardInIncidentChannel, }); if (report) { @@ -148,15 +160,14 @@ export async function onIncidentDeclared( config: SlackConfig, ): Promise { trackSlackIncident(ctx, "declare"); - await notifyCommander(ctx, incidentId, config).catch((error) => - logger.warn("incident commander email failed", { error, incidentId }), - ); - const result = await openIncidentSlackChannel({ - ctx, - incidentId, - clientFor: slackClientFor, - dashboardUrl: config.dashboardUrl, - }); + const incident = await getIncident({ ctx, input: { id: incidentId } }); + const result = (incident && + (await afterIncidentDeclared({ + ctx, + effects: await slackEffects(ctx, config), + incident, + openSlackChannel: true, + }))) ?? { status: "skipped" as const }; logger.info("slack incident channel", { incidentId, ...result }); return result; } @@ -192,27 +203,6 @@ async function offerStatusReportResolve(args: { }); } -async function notifyCommander( - ctx: ServiceContext, - incidentId: number, - config: SlackConfig, -): Promise { - const row = await getIncident({ ctx, input: { id: incidentId } }); - const commander = row?.commander; - const actorUserId = ctx.actor.type === "slack" ? ctx.actor.userId : null; - if (!row || !commander?.email || commander.id === actorUserId) return; - const declarer = row.declaredByUser ? displayName(row.declaredByUser) : null; - await sendIncidentCommander({ - to: commander.email, - incidentTitle: row.title, - severity: row.severity, - workspaceName: ctx.workspace.name ?? ctx.workspace.slug, - assignedBy: declarer ?? "A teammate", - url: `${config.dashboardUrl}/incidents/${row.id}`, - idempotencyKey: `incident-commander:${row.id}:${commander.id}:${row.updatedAt.getTime()}`, - }).catch(() => undefined); -} - /** "Link this channel": the fallback when binding failed during declare. */ export async function bindChannelFromButton(args: { resolved: SlackWorkspace; diff --git a/apps/server/src/routes/slack/interactions.test.ts b/apps/server/src/routes/slack/interactions.test.ts index 02489027..6d1ab957 100644 --- a/apps/server/src/routes/slack/interactions.test.ts +++ b/apps/server/src/routes/slack/interactions.test.ts @@ -18,6 +18,7 @@ import { declareIncident } from "@openstatus/services/incident"; import { beforeEach, describe, expect, test } from "@openstatus/test-utils"; import { Hono } from "hono"; +import { settleBackgroundTasks } from "@/libs/background"; // workspace-resolver / @slack/web-api are swapped for doubles via the test // import map; behavior is driven through this shared mutable state. import { slackTestState } from "@/libs/test/doubles/slack-test-state"; @@ -26,7 +27,6 @@ import { withSlackConfig, } from "@/libs/test/slack-config"; -import { settleBackgroundTasks } from "./background"; import type { SlackEnv } from "./config"; import { handleSlackInteraction } from "./interactions"; import { verifySlackSignature } from "./verify"; diff --git a/apps/server/src/routes/slack/interactions.ts b/apps/server/src/routes/slack/interactions.ts index dae249a4..9bfa2a65 100644 --- a/apps/server/src/routes/slack/interactions.ts +++ b/apps/server/src/routes/slack/interactions.ts @@ -3,7 +3,8 @@ import { ServiceError } from "@openstatus/services"; import { WebClient } from "@slack/web-api"; import type { Context } from "hono"; -import { runInBackground } from "./background"; +import { runInBackground } from "@/libs/background"; + import { buildLinkAccountBlocks, LINK_ACCOUNT_TEXT, diff --git a/apps/server/src/routes/slack/page-urls.ts b/apps/server/src/routes/slack/page-urls.ts index 7699a0d1..0b90a441 100644 --- a/apps/server/src/routes/slack/page-urls.ts +++ b/apps/server/src/routes/slack/page-urls.ts @@ -1,5 +1,9 @@ import { and, db, eq, inArray } from "@openstatus/db"; import { page, pageComponent, statusReport } from "@openstatus/db/src/schema"; +import { + incidentDashboardUrl, + resolveDashboardUrl, +} from "@openstatus/services/incident"; import { env } from "@/env"; @@ -24,13 +28,14 @@ export async function getPageUrl(pageId: number): Promise { } function getDashboardBaseUrl(): string { - return env.NODE_ENV === "production" - ? "https://app.openstatus.dev" - : "http://localhost:3001"; + return resolveDashboardUrl({ + nodeEnv: env.NODE_ENV, + override: env.DASHBOARD_URL, + }); } export function getIncidentDashboardUrl(incidentId: number): string { - return `${getDashboardBaseUrl()}/incidents/${incidentId}`; + return incidentDashboardUrl(getDashboardBaseUrl(), incidentId); } /** diff --git a/apps/server/static/openapi-yaml.ts b/apps/server/static/openapi-yaml.ts index 6a85f645..28820538 100644 --- a/apps/server/static/openapi-yaml.ts +++ b/apps/server/static/openapi-yaml.ts @@ -1,2 +1,2 @@ // Generated from openapi.yaml — run `pnpm --filter @openstatus/server openapi:json`. -export default "openapi: 3.1.0\ninfo:\n description: OpenStatus is a open-source status page platform with global uptime monitoring. The OpenStatus API allows you to interact with the OpenStatus platform programmatically. To get started you need to create an account on https://www.openstatus.dev/ and create an api token in your settings. Requests are rate limited per API key or token (600 per minute, 100 per 10 seconds); exceeding a limit returns HTTP 429 with a Retry-After header. See https://www.openstatus.dev/docs/reference/api-rate-limits.\n title: OpenStatus API\n version: v2.0.0\n contact:\n email: ping@openstatus.dev\n url: https://www.openstatus.dev\nservers:\n - url: https://api.openstatus.dev\n description: Production\nexternalDocs:\n description: OpenStatus Documentation\n url: https://www.openstatus.dev/docs\ncomponents:\n responses:\n RateLimited:\n description: Rate limit exceeded (Connect code resource_exhausted). Retry after the number of seconds in the Retry-After header. See https://www.openstatus.dev/docs/reference/api-rate-limits.\n headers:\n Retry-After:\n description: Seconds to wait before retrying.\n schema:\n type: integer\n example: 7\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n securitySchemes:\n ApiKeyAuth:\n type: apiKey\n in: header\n name: x-openstatus-key\n schemas:\n connect.error:\n type: object\n properties:\n code:\n type: string\n examples:\n - not_found\n enum:\n - canceled\n - unknown\n - invalid_argument\n - deadline_exceeded\n - not_found\n - already_exists\n - permission_denied\n - resource_exhausted\n - failed_precondition\n - aborted\n - out_of_range\n - unimplemented\n - internal\n - unavailable\n - data_loss\n - unauthenticated\n description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].\n message:\n type: string\n description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.\n details:\n type: array\n items:\n $ref: '#/components/schemas/connect.error_details.Any'\n description: A list of messages that carry the error details. There is no limit on the number of messages.\n title: Connect Error\n additionalProperties: true\n description: 'Error type returned by Connect: https://connectrpc.com/docs/go/errors/#http-representation'\n connect.error_details.Any:\n type: object\n properties:\n type:\n type: string\n description: 'A URL that acts as a globally unique identifier for the type of the serialized message. For example: `type.googleapis.com/google.rpc.ErrorInfo`. This is used to determine the schema of the data in the `value` field and is the discriminator for the `debug` field.'\n value:\n type: string\n format: binary\n description: The Protobuf message, serialized as bytes and base64-encoded. The specific message type is identified by the `type` field.\n debug:\n oneOf:\n - type: object\n title: Any\n additionalProperties: true\n description: Detailed error information.\n discriminator:\n propertyName: type\n title: Debug\n description: Deserialized error detail payload. The 'type' field indicates the schema. This field is for easier debugging and should not be relied upon for application logic.\n additionalProperties: true\n description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message, with an additional debug field for ConnectRPC error details.\n openstatus.health.v1.CheckRequest:\n type: object\n properties:\n service:\n type: string\n title: service\n description: Optional service name to check. If empty, checks overall service health.\n title: CheckRequest\n additionalProperties: false\n description: CheckRequest is the request message for health checks.\n openstatus.health.v1.CheckResponse:\n type: object\n properties:\n status:\n title: status\n description: The serving status of the service.\n $ref: '#/components/schemas/openstatus.health.v1.CheckResponse.ServingStatus'\n title: CheckResponse\n additionalProperties: false\n description: CheckResponse is the response message for health checks.\n openstatus.health.v1.CheckResponse.ServingStatus:\n type: string\n title: ServingStatus\n enum:\n - SERVING_STATUS_UNSPECIFIED\n - SERVING_STATUS_SERVING\n - SERVING_STATUS_NOT_SERVING\n description: ServingStatus represents the health status of the service.\n openstatus.maintenance.v1.CreateMaintenanceRequest:\n type: object\n properties:\n title:\n type: string\n examples:\n - Database Migration\n title: title\n maxLength: 256\n minLength: 1\n description: Title of the maintenance (required, 1-256 characters).\n message:\n type: string\n title: message\n minLength: 1\n description: Message describing the maintenance (required).\n from:\n type: string\n examples:\n - \"2024-03-01T02:00:00Z\"\n title: from\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: Start time of the maintenance window (RFC 3339 format, required).\n to:\n type: string\n examples:\n - \"2024-03-01T06:00:00Z\"\n title: to\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: End time of the maintenance window (RFC 3339 format, required).\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: Page ID to associate with this maintenance (required).\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: Page component IDs to associate with this maintenance (optional).\n notify:\n type:\n - boolean\n - \"null\"\n title: notify\n description: Whether to notify subscribers about this maintenance (optional, defaults to false).\n title: CreateMaintenanceRequest\n additionalProperties: false\n description: CreateMaintenanceRequest is the request to create a new maintenance window.\n openstatus.maintenance.v1.CreateMaintenanceResponse:\n type: object\n properties:\n maintenance:\n title: maintenance\n description: The created maintenance.\n $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance'\n title: CreateMaintenanceResponse\n additionalProperties: false\n description: CreateMaintenanceResponse is the response after creating a maintenance window.\n openstatus.maintenance.v1.DeleteMaintenanceRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the maintenance to delete (required).\n title: DeleteMaintenanceRequest\n additionalProperties: false\n description: DeleteMaintenanceRequest is the request to delete a maintenance window.\n openstatus.maintenance.v1.DeleteMaintenanceResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteMaintenanceResponse\n additionalProperties: false\n description: DeleteMaintenanceResponse is the response after deleting a maintenance window.\n openstatus.maintenance.v1.GetMaintenanceRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the maintenance to retrieve (required).\n title: GetMaintenanceRequest\n additionalProperties: false\n description: GetMaintenanceRequest is the request to get a maintenance window by ID.\n openstatus.maintenance.v1.GetMaintenanceResponse:\n type: object\n properties:\n maintenance:\n title: maintenance\n description: The requested maintenance.\n $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance'\n title: GetMaintenanceResponse\n additionalProperties: false\n description: GetMaintenanceResponse is the response containing the maintenance window.\n openstatus.maintenance.v1.ListMaintenancesRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of maintenances to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of maintenances to skip for pagination (defaults to 0).\n pageId:\n type:\n - string\n - \"null\"\n title: page_id\n description: Filter by page ID (optional).\n title: ListMaintenancesRequest\n additionalProperties: false\n description: ListMaintenancesRequest is the request to list maintenance windows.\n openstatus.maintenance.v1.ListMaintenancesResponse:\n type: object\n properties:\n maintenances:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary'\n title: maintenances\n description: List of maintenances.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of maintenances matching the filter.\n title: ListMaintenancesResponse\n additionalProperties: false\n description: ListMaintenancesResponse is the response containing maintenance window summaries.\n openstatus.maintenance.v1.Maintenance:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the maintenance.\n title:\n type: string\n title: title\n description: Title of the maintenance.\n message:\n type: string\n title: message\n description: Message describing the maintenance.\n from:\n type: string\n title: from\n description: Start time of the maintenance window (RFC 3339 format).\n to:\n type: string\n title: to\n description: End time of the maintenance window (RFC 3339 format).\n pageId:\n type: string\n title: page_id\n description: ID of the page this maintenance is associated with.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the maintenance was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the maintenance was last updated (RFC 3339 format).\n title: Maintenance\n additionalProperties: false\n description: Maintenance represents a maintenance window with full details.\n openstatus.maintenance.v1.MaintenanceSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the maintenance.\n title:\n type: string\n title: title\n description: Title of the maintenance.\n message:\n type: string\n title: message\n description: Message describing the maintenance.\n from:\n type: string\n title: from\n description: Start time of the maintenance window (RFC 3339 format).\n to:\n type: string\n title: to\n description: End time of the maintenance window (RFC 3339 format).\n pageId:\n type: string\n title: page_id\n description: ID of the page this maintenance is associated with.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the maintenance was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the maintenance was last updated (RFC 3339 format).\n title: MaintenanceSummary\n additionalProperties: false\n description: MaintenanceSummary represents metadata for a maintenance window (used in list responses).\n openstatus.maintenance.v1.UpdateMaintenanceRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the maintenance to update (required).\n title:\n type:\n - string\n - \"null\"\n title: title\n maxLength: 256\n minLength: 1\n description: New title for the maintenance (optional).\n message:\n type:\n - string\n - \"null\"\n title: message\n description: New message for the maintenance (optional).\n from:\n type:\n - string\n - \"null\"\n title: from\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: New start time (RFC 3339 format, optional).\n to:\n type:\n - string\n - \"null\"\n title: to\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: New end time (RFC 3339 format, optional).\n pageId:\n type:\n - string\n - \"null\"\n title: page_id\n description: 'Deprecated: page_id is now derived from page_component_ids.'\n deprecated: true\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: New list of page component IDs (optional, replaces existing list).\n updatePageComponentIds:\n type:\n - boolean\n - \"null\"\n title: update_page_component_ids\n description: |-\n Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved.\n title: UpdateMaintenanceRequest\n additionalProperties: false\n description: UpdateMaintenanceRequest is the request to update a maintenance window.\n openstatus.maintenance.v1.UpdateMaintenanceResponse:\n type: object\n properties:\n maintenance:\n title: maintenance\n description: The updated maintenance.\n $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance'\n title: UpdateMaintenanceResponse\n additionalProperties: false\n description: UpdateMaintenanceResponse is the response after updating a maintenance window.\n openstatus.monitor.v1.BodyAssertion:\n type: object\n properties:\n target:\n type: string\n title: target\n description: Target value to compare against.\n comparator:\n not:\n enum:\n - STRING_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator'\n title: BodyAssertion\n additionalProperties: false\n description: BodyAssertion defines an assertion for response body content.\n openstatus.monitor.v1.CreateDNSMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: CreateDNSMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateDNSMonitorRequest is the request to create a new DNS monitor.\n openstatus.monitor.v1.CreateDNSMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: CreateDNSMonitorResponse\n additionalProperties: false\n description: CreateDNSMonitorResponse is the response after creating a DNS monitor.\n openstatus.monitor.v1.CreateGRPCMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: CreateGRPCMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateGRPCMonitorRequest is the request to create a new gRPC monitor.\n openstatus.monitor.v1.CreateGRPCMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: CreateGRPCMonitorResponse\n additionalProperties: false\n description: CreateGRPCMonitorResponse is the response after creating a gRPC monitor.\n openstatus.monitor.v1.CreateHTTPMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: CreateHTTPMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateHTTPMonitorRequest is the request to create a new HTTP monitor.\n openstatus.monitor.v1.CreateHTTPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: CreateHTTPMonitorResponse\n additionalProperties: false\n description: CreateHTTPMonitorResponse is the response after creating an HTTP monitor.\n openstatus.monitor.v1.CreateICMPMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: CreateICMPMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateICMPMonitorRequest is the request to create a new ICMP monitor.\n openstatus.monitor.v1.CreateICMPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: CreateICMPMonitorResponse\n additionalProperties: false\n description: CreateICMPMonitorResponse is the response after creating an ICMP monitor.\n openstatus.monitor.v1.CreateTCPMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: CreateTCPMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateTCPMonitorRequest is the request to create a new TCP monitor.\n openstatus.monitor.v1.CreateTCPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: CreateTCPMonitorResponse\n additionalProperties: false\n description: CreateTCPMonitorResponse is the response after creating a TCP monitor.\n openstatus.monitor.v1.DNSMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - DNS Resolution Check\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - example.com\n title: uri\n maxLength: 2048\n minLength: 1\n description: Domain to resolve (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n recordAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.RecordAssertion'\n title: record_assertions\n maxItems: 10\n description: DNS record assertions for validation.\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: DNSMonitor\n additionalProperties: false\n description: DNSMonitor defines the configuration for a DNS monitor.\n openstatus.monitor.v1.DeleteMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to delete (required).\n title: DeleteMonitorRequest\n additionalProperties: false\n description: DeleteMonitorRequest is the request to delete a monitor.\n openstatus.monitor.v1.DeleteMonitorResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteMonitorResponse\n additionalProperties: false\n description: DeleteMonitorResponse is the response after deleting a monitor.\n openstatus.monitor.v1.GRPCMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Checkout gRPC\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - api.example.com:443\n title: uri\n maxLength: 2048\n minLength: 1\n pattern: ^(\\[[0-9a-fA-F:]+\\]|[^:/\\s]+):[0-9]{1,5}$\n description: Target in \"host:port\" form. IPv6 addresses must be bracketed.\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n service:\n type:\n - string\n - \"null\"\n examples:\n - checkout.v1.CheckoutService\n title: service\n maxLength: 512\n description: Service name passed to Health/Check. Empty means overall server health.\n tlsMode:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.GRPCTlsMode'\n - type: \"null\"\n title: tls_mode\n description: How the connection to the target is secured. Defaults to TLS.\n metadata:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Headers'\n title: metadata\n maxItems: 20\n description: Metadata sent with the health check request.\n title: GRPCMonitor\n additionalProperties: false\n description: |-\n GRPCMonitor defines the configuration for a gRPC health check monitor.\n The probe calls grpc.health.v1.Health/Check on the target.\n openstatus.monitor.v1.GRPCTlsMode:\n type: string\n title: GRPCTlsMode\n enum:\n - GRPC_TLS_MODE_UNSPECIFIED\n - GRPC_TLS_MODE_TLS\n - GRPC_TLS_MODE_PLAINTEXT\n - GRPC_TLS_MODE_TLS_INSECURE\n description: GRPCTlsMode selects how the probe secures its connection to the target.\n openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to get a response log for (required).\n logId:\n type: string\n title: log_id\n minLength: 1\n description: Response log ID to retrieve (required).\n title: GetMonitorHTTPResponseLogRequest\n additionalProperties: false\n description: GetMonitorHTTPResponseLogRequest is the request to get one response log.\n openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse:\n type: object\n properties:\n log:\n title: log\n description: Response log details.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogDetail'\n title: GetMonitorHTTPResponseLogResponse\n additionalProperties: false\n description: GetMonitorHTTPResponseLogResponse is the response containing one response log.\n openstatus.monitor.v1.GetMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to retrieve (required).\n title: GetMonitorRequest\n additionalProperties: false\n description: GetMonitorRequest is the request to get a single monitor by ID.\n openstatus.monitor.v1.GetMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The monitor configuration (one of HTTP, TCP, DNS, ICMP, or gRPC).\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorConfig'\n title: GetMonitorResponse\n additionalProperties: false\n description: GetMonitorResponse is the response containing the monitor.\n openstatus.monitor.v1.GetMonitorStatusRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to get status for (required).\n title: GetMonitorStatusRequest\n additionalProperties: false\n description: GetMonitorStatusRequest is the request to get the status of all regions for a monitor.\n openstatus.monitor.v1.GetMonitorStatusResponse:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Monitor ID.\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.RegionStatus'\n title: regions\n description: Status for each region.\n title: GetMonitorStatusResponse\n additionalProperties: false\n description: GetMonitorStatusResponse is the response containing the status of all regions for a monitor.\n openstatus.monitor.v1.GetMonitorSummaryRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to get summary for (required).\n timeRange:\n title: time_range\n description: Time range for metrics aggregation (defaults to 1 day if unspecified).\n $ref: '#/components/schemas/openstatus.monitor.v1.TimeRange'\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Optional filter by regions. If empty, returns metrics for all regions.\n title: GetMonitorSummaryRequest\n additionalProperties: false\n description: GetMonitorSummaryRequest is the request to get aggregated metrics for a monitor.\n openstatus.monitor.v1.GetMonitorSummaryResponse:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Monitor ID.\n lastPingAt:\n type: string\n title: last_ping_at\n description: Timestamp of the last check in RFC 3339 format.\n totalSuccessful:\n type:\n - integer\n - string\n title: total_successful\n format: int64\n description: Total number of successful requests.\n totalDegraded:\n type:\n - integer\n - string\n title: total_degraded\n format: int64\n description: Total number of degraded requests.\n totalFailed:\n type:\n - integer\n - string\n title: total_failed\n format: int64\n description: Total number of failed requests.\n p50:\n type:\n - integer\n - string\n title: p50\n format: int64\n description: 50th percentile (median) latency in milliseconds.\n p75:\n type:\n - integer\n - string\n title: p75\n format: int64\n description: 75th percentile latency in milliseconds.\n p90:\n type:\n - integer\n - string\n title: p90\n format: int64\n description: 90th percentile latency in milliseconds.\n p95:\n type:\n - integer\n - string\n title: p95\n format: int64\n description: 95th percentile latency in milliseconds.\n p99:\n type:\n - integer\n - string\n title: p99\n format: int64\n description: 99th percentile latency in milliseconds.\n timeRange:\n title: time_range\n description: Time range used for the metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.TimeRange'\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n description: Regions included in the metrics.\n title: GetMonitorSummaryResponse\n additionalProperties: false\n description: GetMonitorSummaryResponse is the response containing aggregated metrics for a monitor.\n openstatus.monitor.v1.HTTPMethod:\n type: string\n title: HTTPMethod\n enum:\n - HTTP_METHOD_UNSPECIFIED\n - HTTP_METHOD_GET\n - HTTP_METHOD_POST\n - HTTP_METHOD_HEAD\n - HTTP_METHOD_PUT\n - HTTP_METHOD_PATCH\n - HTTP_METHOD_DELETE\n - HTTP_METHOD_TRACE\n - HTTP_METHOD_CONNECT\n - HTTP_METHOD_OPTIONS\n description: HTTP methods supported for monitors.\n openstatus.monitor.v1.HTTPMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Production API Health Check\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n url:\n type: string\n examples:\n - https://api.example.com/health\n title: url\n maxLength: 2048\n minLength: 1\n format: uri\n description: URL to monitor (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n method:\n not:\n enum:\n - HTTP_METHOD_UNSPECIFIED\n title: method\n description: HTTP method to use (defaults to GET).\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMethod'\n body:\n type: string\n examples:\n - map[key:value]\n title: body\n description: Request body (optional).\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n followRedirects:\n type:\n - boolean\n - \"null\"\n title: follow_redirects\n description: Whether to follow HTTP redirects (defaults to true when not specified).\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Headers'\n title: headers\n maxItems: 20\n description: Custom headers for the request.\n statusCodeAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.StatusCodeAssertion'\n title: status_code_assertions\n maxItems: 10\n description: Status code assertions for the response.\n bodyAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.BodyAssertion'\n title: body_assertions\n maxItems: 10\n description: Body content assertions for the response.\n headerAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.HeaderAssertion'\n title: header_assertions\n maxItems: 10\n description: Header assertions for the response.\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: HTTPMonitor\n additionalProperties: false\n description: HTTPMonitor defines the configuration for an HTTP monitor.\n openstatus.monitor.v1.HTTPResponseLogDetail:\n type: object\n properties:\n log:\n title: log\n description: Compact response log fields.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem'\n url:\n type: string\n title: url\n description: Checked URL.\n error:\n type: boolean\n title: error\n description: Whether the check errored.\n message:\n type:\n - string\n - \"null\"\n title: message\n description: Error message, when present.\n headers:\n type: object\n title: headers\n additionalProperties:\n type: string\n title: value\n description: Redacted response headers.\n assertions:\n type:\n - string\n - \"null\"\n title: assertions\n description: Serialized assertions used for the check.\n title: HTTPResponseLogDetail\n additionalProperties: false\n description: HTTPResponseLogDetail contains full response log debugging data.\n openstatus.monitor.v1.HTTPResponseLogDetail.HeadersEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: HeadersEntry\n additionalProperties: false\n openstatus.monitor.v1.HTTPResponseLogListItem:\n type: object\n properties:\n id:\n type:\n - string\n - \"null\"\n title: id\n description: Response log ID.\n latency:\n type: integer\n title: latency\n format: int32\n description: Latency in milliseconds.\n statusCode:\n type:\n - integer\n - \"null\"\n title: status_code\n format: int32\n description: HTTP status code.\n monitorId:\n type: string\n title: monitor_id\n description: Monitor ID.\n requestStatus:\n title: request_status\n description: Request status classification.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogRequestStatus'\n region:\n title: region\n description: Region where the check ran.\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n cronTimestamp:\n type:\n - integer\n - string\n title: cron_timestamp\n format: int64\n description: Cron bucket timestamp in Unix milliseconds.\n trigger:\n title: trigger\n description: Check trigger.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTrigger'\n timestamp:\n type:\n - integer\n - string\n title: timestamp\n format: int64\n description: Response timestamp in Unix milliseconds.\n timing:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTiming'\n - type: \"null\"\n title: timing\n description: Timing phases.\n title: HTTPResponseLogListItem\n additionalProperties: false\n description: HTTPResponseLogListItem is a compact response log entry.\n openstatus.monitor.v1.HTTPResponseLogPagination:\n type: object\n properties:\n limit:\n type: integer\n title: limit\n format: int32\n description: Requested page size.\n offset:\n type: integer\n title: offset\n format: int32\n description: Requested offset.\n hasMore:\n type: boolean\n title: has_more\n description: Whether more logs are available.\n nextOffset:\n type:\n - integer\n - \"null\"\n title: next_offset\n format: int32\n description: Next offset if more logs are available.\n title: HTTPResponseLogPagination\n additionalProperties: false\n description: HTTPResponseLogPagination contains offset pagination metadata.\n openstatus.monitor.v1.HTTPResponseLogRequestStatus:\n type: string\n title: HTTPResponseLogRequestStatus\n enum:\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_UNSPECIFIED\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_SUCCESS\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_ERROR\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_DEGRADED\n description: HTTPResponseLogRequestStatus is the result classification for an HTTP response log.\n openstatus.monitor.v1.HTTPResponseLogTiming:\n type: object\n properties:\n dns:\n type: integer\n title: dns\n format: int32\n description: DNS lookup duration.\n connect:\n type: integer\n title: connect\n format: int32\n description: TCP connection duration.\n tls:\n type: integer\n title: tls\n format: int32\n description: TLS handshake duration.\n ttfb:\n type: integer\n title: ttfb\n format: int32\n description: Time to first byte duration.\n transfer:\n type: integer\n title: transfer\n format: int32\n description: Response transfer duration.\n title: HTTPResponseLogTiming\n additionalProperties: false\n description: HTTPResponseLogTiming contains calculated timing phases in milliseconds.\n openstatus.monitor.v1.HTTPResponseLogTrigger:\n type: string\n title: HTTPResponseLogTrigger\n enum:\n - HTTP_RESPONSE_LOG_TRIGGER_UNSPECIFIED\n - HTTP_RESPONSE_LOG_TRIGGER_CRON\n - HTTP_RESPONSE_LOG_TRIGGER_API\n description: HTTPResponseLogTrigger describes what started the monitor check.\n openstatus.monitor.v1.HeaderAssertion:\n type: object\n properties:\n target:\n type: string\n title: target\n description: Target value to compare against.\n comparator:\n not:\n enum:\n - STRING_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator'\n key:\n type: string\n title: key\n minLength: 1\n description: Header key to check (required).\n title: HeaderAssertion\n additionalProperties: false\n description: HeaderAssertion defines an assertion for response headers.\n openstatus.monitor.v1.Headers:\n type: object\n properties:\n key:\n type: string\n examples:\n - Authorization\n title: key\n minLength: 1\n description: Header name.\n value:\n type: string\n examples:\n - Bearer token123\n title: value\n description: Header value.\n title: Headers\n additionalProperties: false\n description: Headers represents a key-value pair for HTTP headers.\n openstatus.monitor.v1.ICMPMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Ping Gateway\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - 1.1.1.1\n title: uri\n maxLength: 2048\n minLength: 1\n description: URI to monitor in format \"host or IP\" (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: ICMPMonitor\n additionalProperties: false\n description: ICMPMonitor defines the configuration for a ICMP monitor.\n openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to list response logs for (required).\n fromTimestamp:\n type:\n - integer\n - string\n - \"null\"\n title: from_timestamp\n format: int64\n description: Start of the response log window as Unix milliseconds within the 14-day retention window.\n toTimestamp:\n type:\n - integer\n - string\n - \"null\"\n title: to_timestamp\n format: int64\n description: End of the response log window as Unix milliseconds within the 14-day retention window.\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of logs to return (1-100, defaults to 25).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of logs to skip for pagination (defaults to 0).\n title: ListMonitorHTTPResponseLogsRequest\n additionalProperties: false\n description: ListMonitorHTTPResponseLogsRequest is the request to list response logs within the 14-day HTTP response-log window.\n openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse:\n type: object\n properties:\n logs:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem'\n title: logs\n description: Response logs.\n pagination:\n title: pagination\n description: Pagination metadata.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogPagination'\n title: ListMonitorHTTPResponseLogsResponse\n additionalProperties: false\n description: ListMonitorHTTPResponseLogsResponse is the response containing response logs.\n openstatus.monitor.v1.ListMonitorsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of monitors to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of monitors to skip for pagination (defaults to 0).\n title: ListMonitorsRequest\n additionalProperties: false\n description: ListMonitorsRequest is the request to list monitors.\n openstatus.monitor.v1.ListMonitorsResponse:\n type: object\n properties:\n httpMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: http_monitors\n description: HTTP monitors in the workspace.\n tcpMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: tcp_monitors\n description: TCP monitors in the workspace.\n dnsMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: dns_monitors\n description: DNS monitors in the workspace.\n icmpMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: icmp_monitors\n description: ICMP monitors in the workspace.\n grpcMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: grpc_monitors\n description: gRPC monitors in the workspace.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of monitors across all types.\n title: ListMonitorsResponse\n additionalProperties: false\n description: ListMonitorsResponse is the response containing a list of monitors.\n openstatus.monitor.v1.MonitorConfig:\n type: object\n oneOf:\n - type: object\n properties:\n dns:\n title: dns\n description: DNS monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: dns\n required:\n - dns\n - type: object\n properties:\n grpc:\n title: grpc\n description: gRPC monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: grpc\n required:\n - grpc\n - type: object\n properties:\n http:\n title: http\n description: HTTP monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: http\n required:\n - http\n - type: object\n properties:\n icmp:\n title: icmp\n description: ICMP monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: icmp\n required:\n - icmp\n - type: object\n properties:\n tcp:\n title: tcp\n description: TCP monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: tcp\n required:\n - tcp\n title: MonitorConfig\n additionalProperties: false\n description: MonitorConfig represents the type-specific configuration for a monitor.\n openstatus.monitor.v1.MonitorStatus:\n type: string\n title: MonitorStatus\n enum:\n - MONITOR_STATUS_UNSPECIFIED\n - MONITOR_STATUS_ACTIVE\n - MONITOR_STATUS_DEGRADED\n - MONITOR_STATUS_ERROR\n description: MonitorStatus represents the operational status of a monitor.\n openstatus.monitor.v1.NumberComparator:\n type: string\n title: NumberComparator\n enum:\n - NUMBER_COMPARATOR_UNSPECIFIED\n - NUMBER_COMPARATOR_EQUAL\n - NUMBER_COMPARATOR_NOT_EQUAL\n - NUMBER_COMPARATOR_GREATER_THAN\n - NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL\n - NUMBER_COMPARATOR_LESS_THAN\n - NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL\n description: NumberComparator defines comparison operations for numeric values.\n openstatus.monitor.v1.OpenTelemetryConfig:\n type: object\n properties:\n endpoint:\n type: string\n title: endpoint\n maxLength: 2048\n description: OTEL endpoint URL.\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Headers'\n title: headers\n maxItems: 20\n description: Custom headers for OTEL requests.\n title: OpenTelemetryConfig\n additionalProperties: false\n description: OpenTelemetry configuration for exporting metrics.\n openstatus.monitor.v1.Periodicity:\n type: string\n title: Periodicity\n enum:\n - PERIODICITY_UNSPECIFIED\n - PERIODICITY_30S\n - PERIODICITY_1M\n - PERIODICITY_5M\n - PERIODICITY_10M\n - PERIODICITY_30M\n - PERIODICITY_1H\n description: Monitor periodicity options.\n openstatus.monitor.v1.RecordAssertion:\n type: object\n properties:\n record:\n type: string\n title: record\n enum:\n - A\n - AAAA\n - CNAME\n - MX\n - TXT\n description: DNS record type (e.g., \"A\", \"AAAA\", \"CNAME\", \"MX\", \"TXT\").\n comparator:\n not:\n enum:\n - RECORD_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.RecordComparator'\n target:\n type: string\n title: target\n description: Target value to compare against.\n title: RecordAssertion\n additionalProperties: false\n description: RecordAssertion defines an assertion for DNS records.\n openstatus.monitor.v1.RecordComparator:\n type: string\n title: RecordComparator\n enum:\n - RECORD_COMPARATOR_UNSPECIFIED\n - RECORD_COMPARATOR_EQUAL\n - RECORD_COMPARATOR_NOT_EQUAL\n - RECORD_COMPARATOR_CONTAINS\n - RECORD_COMPARATOR_NOT_CONTAINS\n description: RecordComparator defines comparison operations for DNS records.\n openstatus.monitor.v1.Region:\n type: string\n title: Region\n enum:\n - REGION_UNSPECIFIED\n - REGION_FLY_AMS\n - REGION_FLY_ARN\n - REGION_FLY_BOM\n - REGION_FLY_CDG\n - REGION_FLY_DFW\n - REGION_FLY_EWR\n - REGION_FLY_FRA\n - REGION_FLY_GRU\n - REGION_FLY_IAD\n - REGION_FLY_JNB\n - REGION_FLY_LAX\n - REGION_FLY_LHR\n - REGION_FLY_NRT\n - REGION_FLY_ORD\n - REGION_FLY_SJC\n - REGION_FLY_SIN\n - REGION_FLY_SYD\n - REGION_FLY_YYZ\n - REGION_KOYEB_FRA\n - REGION_KOYEB_PAR\n - REGION_KOYEB_SFO\n - REGION_KOYEB_SIN\n - REGION_KOYEB_TYO\n - REGION_KOYEB_WAS\n - REGION_RAILWAY_US_WEST2\n - REGION_RAILWAY_US_EAST4\n - REGION_RAILWAY_EUROPE_WEST4\n - REGION_RAILWAY_ASIA_SOUTHEAST1\n description: |-\n Geographic regions where monitors can run checks from.\n REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN.\n openstatus.monitor.v1.RegionStatus:\n type: object\n properties:\n region:\n title: region\n description: The region identifier.\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n status:\n title: status\n description: The status of the monitor in this region.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n title: RegionStatus\n additionalProperties: false\n description: RegionStatus represents the status of a monitor in a specific region.\n openstatus.monitor.v1.StatusCodeAssertion:\n type: object\n properties:\n target:\n type:\n - integer\n - string\n title: target\n maximum: 599\n minimum: 100\n format: int64\n description: Target status code to compare against (100-599).\n comparator:\n not:\n enum:\n - NUMBER_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.NumberComparator'\n title: StatusCodeAssertion\n additionalProperties: false\n description: StatusCodeAssertion defines an assertion for HTTP status codes.\n openstatus.monitor.v1.StringComparator:\n type: string\n title: StringComparator\n enum:\n - STRING_COMPARATOR_UNSPECIFIED\n - STRING_COMPARATOR_CONTAINS\n - STRING_COMPARATOR_NOT_CONTAINS\n - STRING_COMPARATOR_EQUAL\n - STRING_COMPARATOR_NOT_EQUAL\n - STRING_COMPARATOR_EMPTY\n - STRING_COMPARATOR_NOT_EMPTY\n - STRING_COMPARATOR_GREATER_THAN\n - STRING_COMPARATOR_GREATER_THAN_OR_EQUAL\n - STRING_COMPARATOR_LESS_THAN\n - STRING_COMPARATOR_LESS_THAN_OR_EQUAL\n description: StringComparator defines comparison operations for string values.\n openstatus.monitor.v1.TCPMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Database Connection Check\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - tcp://db.example.com:5432\n title: uri\n maxLength: 2048\n minLength: 1\n description: URI to monitor in format \"host:port\" (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: TCPMonitor\n additionalProperties: false\n description: TCPMonitor defines the configuration for a TCP monitor.\n openstatus.monitor.v1.TimeRange:\n type: string\n title: TimeRange\n enum:\n - TIME_RANGE_UNSPECIFIED\n - TIME_RANGE_1D\n - TIME_RANGE_7D\n - TIME_RANGE_14D\n description: TimeRange represents the time period for metrics aggregation.\n openstatus.monitor.v1.TriggerMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to trigger (required).\n title: TriggerMonitorRequest\n additionalProperties: false\n description: TriggerMonitorRequest is the request to trigger a monitor check.\n openstatus.monitor.v1.TriggerMonitorResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the trigger was successful.\n title: TriggerMonitorResponse\n additionalProperties: false\n description: TriggerMonitorResponse is the response after triggering a monitor.\n openstatus.monitor.v1.UpdateDNSMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateDNSMonitorRequest\n additionalProperties: false\n description: UpdateDNSMonitorRequest is the request to update an existing DNS monitor.\n openstatus.monitor.v1.UpdateDNSMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: UpdateDNSMonitorResponse\n additionalProperties: false\n description: UpdateDNSMonitorResponse is the response after updating a DNS monitor.\n openstatus.monitor.v1.UpdateGRPCMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateGRPCMonitorRequest\n additionalProperties: false\n description: UpdateGRPCMonitorRequest is the request to update an existing gRPC monitor.\n openstatus.monitor.v1.UpdateGRPCMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: UpdateGRPCMonitorResponse\n additionalProperties: false\n description: UpdateGRPCMonitorResponse is the response after updating a gRPC monitor.\n openstatus.monitor.v1.UpdateHTTPMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateHTTPMonitorRequest\n additionalProperties: false\n description: UpdateHTTPMonitorRequest is the request to update an existing HTTP monitor.\n openstatus.monitor.v1.UpdateHTTPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: UpdateHTTPMonitorResponse\n additionalProperties: false\n description: UpdateHTTPMonitorResponse is the response after updating an HTTP monitor.\n openstatus.monitor.v1.UpdateICMPMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateICMPMonitorRequest\n additionalProperties: false\n description: UpdateICMPMonitorRequest is the request to update an existing ICMP monitor.\n openstatus.monitor.v1.UpdateICMPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: UpdateICMPMonitorResponse\n additionalProperties: false\n description: UpdateICMPMonitorResponse is the response after updating an ICMP monitor.\n openstatus.monitor.v1.UpdateTCPMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateTCPMonitorRequest\n additionalProperties: false\n description: UpdateTCPMonitorRequest is the request to update an existing TCP monitor.\n openstatus.monitor.v1.UpdateTCPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: UpdateTCPMonitorResponse\n additionalProperties: false\n description: UpdateTCPMonitorResponse is the response after updating a TCP monitor.\n openstatus.notification.v1.CheckNotificationLimitRequest:\n type: object\n title: CheckNotificationLimitRequest\n additionalProperties: false\n description: CheckNotificationLimitRequest is the request to check notification limits.\n openstatus.notification.v1.CheckNotificationLimitResponse:\n type: object\n properties:\n limitReached:\n type: boolean\n title: limit_reached\n description: Whether the workspace has reached its notification limit.\n currentCount:\n type: integer\n title: current_count\n format: int32\n description: Current number of notification channels.\n maxCount:\n type: integer\n title: max_count\n format: int32\n description: Maximum allowed notification channels.\n title: CheckNotificationLimitResponse\n additionalProperties: false\n description: CheckNotificationLimitResponse is the response containing limit information.\n openstatus.notification.v1.CreateNotificationRequest:\n type: object\n properties:\n name:\n type: string\n examples:\n - Slack Ops Channel\n title: name\n minLength: 1\n description: Display name for the notification channel.\n provider:\n not:\n enum:\n - NOTIFICATION_PROVIDER_UNSPECIFIED\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n data:\n title: data\n description: Provider-specific configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors to associate with this notification.\n title: CreateNotificationRequest\n required:\n - data\n additionalProperties: false\n description: CreateNotificationRequest is the request to create a new notification channel.\n openstatus.notification.v1.CreateNotificationResponse:\n type: object\n properties:\n notification:\n title: notification\n description: The created notification channel.\n $ref: '#/components/schemas/openstatus.notification.v1.Notification'\n title: CreateNotificationResponse\n additionalProperties: false\n description: CreateNotificationResponse is the response after creating a notification channel.\n openstatus.notification.v1.DeleteNotificationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Notification ID to delete (required).\n title: DeleteNotificationRequest\n additionalProperties: false\n description: DeleteNotificationRequest is the request to delete a notification channel.\n openstatus.notification.v1.DeleteNotificationResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteNotificationResponse\n additionalProperties: false\n description: DeleteNotificationResponse is the response after deleting a notification channel.\n openstatus.notification.v1.DiscordData:\n type: object\n properties:\n webhookUrl:\n type: string\n examples:\n - https://discord.com/api/webhooks/123/abc\n title: webhook_url\n format: uri\n description: Discord webhook URL.\n title: DiscordData\n additionalProperties: false\n description: DiscordData contains configuration for Discord notifications.\n openstatus.notification.v1.EmailData:\n type: object\n properties:\n email:\n type: string\n examples:\n - ops-team@example.com\n title: email\n format: email\n description: Email address to send notifications to.\n title: EmailData\n additionalProperties: false\n description: EmailData contains configuration for email notifications.\n openstatus.notification.v1.GetNotificationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Notification ID to retrieve (required).\n title: GetNotificationRequest\n additionalProperties: false\n description: GetNotificationRequest is the request to get a notification channel.\n openstatus.notification.v1.GetNotificationResponse:\n type: object\n properties:\n notification:\n title: notification\n description: The notification channel.\n $ref: '#/components/schemas/openstatus.notification.v1.Notification'\n title: GetNotificationResponse\n additionalProperties: false\n description: GetNotificationResponse is the response containing the notification channel.\n openstatus.notification.v1.GoogleChatData:\n type: object\n properties:\n webhookUrl:\n type: string\n title: webhook_url\n format: uri\n description: Google Chat webhook URL.\n title: GoogleChatData\n additionalProperties: false\n description: GoogleChatData contains configuration for Google Chat notifications.\n openstatus.notification.v1.GrafanaOncallData:\n type: object\n properties:\n webhookUrl:\n type: string\n title: webhook_url\n format: uri\n description: Grafana OnCall webhook URL.\n title: GrafanaOncallData\n additionalProperties: false\n description: GrafanaOncallData contains configuration for Grafana OnCall notifications.\n openstatus.notification.v1.ListNotificationsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of notifications to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of notifications to skip for pagination (defaults to 0).\n title: ListNotificationsRequest\n additionalProperties: false\n description: ListNotificationsRequest is the request to list notification channels.\n openstatus.notification.v1.ListNotificationsResponse:\n type: object\n properties:\n notifications:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationSummary'\n title: notifications\n description: Notification channel summaries.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of notification channels.\n title: ListNotificationsResponse\n additionalProperties: false\n description: ListNotificationsResponse is the response containing notification channels.\n openstatus.notification.v1.MsTeamsData:\n type: object\n properties:\n webhookUrl:\n type: string\n examples:\n - https://prod-00.westeurope.logic.azure.com:443/workflows/abc/triggers/manual/paths/invoke\n title: webhook_url\n format: uri\n description: Microsoft Teams webhook URL (Power Automate Workflows).\n title: MsTeamsData\n additionalProperties: false\n description: MsTeamsData contains configuration for Microsoft Teams notifications.\n openstatus.notification.v1.Notification:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the notification.\n name:\n type: string\n title: name\n description: Display name for the notification channel.\n provider:\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n data:\n title: data\n description: Provider-specific configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors associated with this notification.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the notification was created (RFC 3339).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the notification was last updated (RFC 3339).\n title: Notification\n additionalProperties: false\n description: Notification represents a notification channel with full details.\n openstatus.notification.v1.NotificationData:\n type: object\n oneOf:\n - type: object\n properties:\n discord:\n title: discord\n description: Discord configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.DiscordData'\n title: discord\n required:\n - discord\n - type: object\n properties:\n email:\n title: email\n description: Email configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.EmailData'\n title: email\n required:\n - email\n - type: object\n properties:\n googleChat:\n title: google_chat\n description: Google Chat configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.GoogleChatData'\n title: google_chat\n required:\n - googleChat\n - type: object\n properties:\n grafanaOncall:\n title: grafana_oncall\n description: Grafana OnCall configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.GrafanaOncallData'\n title: grafana_oncall\n required:\n - grafanaOncall\n - type: object\n properties:\n msTeams:\n title: ms_teams\n description: Microsoft Teams configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.MsTeamsData'\n title: ms_teams\n required:\n - msTeams\n - type: object\n properties:\n ntfy:\n title: ntfy\n description: Ntfy configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NtfyData'\n title: ntfy\n required:\n - ntfy\n - type: object\n properties:\n opsgenie:\n title: opsgenie\n description: Opsgenie configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.OpsgenieData'\n title: opsgenie\n required:\n - opsgenie\n - type: object\n properties:\n pagerduty:\n title: pagerduty\n description: PagerDuty configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.PagerDutyData'\n title: pagerduty\n required:\n - pagerduty\n - type: object\n properties:\n slack:\n title: slack\n description: Slack configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.SlackData'\n title: slack\n required:\n - slack\n - type: object\n properties:\n sms:\n title: sms\n description: 'Deprecated: SMS is no longer offered; use whatsapp.'\n deprecated: true\n $ref: '#/components/schemas/openstatus.notification.v1.SmsData'\n title: sms\n required:\n - sms\n - type: object\n properties:\n telegram:\n title: telegram\n description: Telegram configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.TelegramData'\n title: telegram\n required:\n - telegram\n - type: object\n properties:\n webhook:\n title: webhook\n description: Webhook configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.WebhookData'\n title: webhook\n required:\n - webhook\n - type: object\n properties:\n whatsapp:\n title: whatsapp\n description: WhatsApp configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.WhatsappData'\n title: whatsapp\n required:\n - whatsapp\n title: NotificationData\n additionalProperties: false\n description: NotificationData is a union of provider-specific configuration.\n openstatus.notification.v1.NotificationProvider:\n type: string\n title: NotificationProvider\n enum:\n - NOTIFICATION_PROVIDER_UNSPECIFIED\n - NOTIFICATION_PROVIDER_DISCORD\n - NOTIFICATION_PROVIDER_EMAIL\n - NOTIFICATION_PROVIDER_GOOGLE_CHAT\n - NOTIFICATION_PROVIDER_GRAFANA_ONCALL\n - NOTIFICATION_PROVIDER_NTFY\n - NOTIFICATION_PROVIDER_PAGERDUTY\n - NOTIFICATION_PROVIDER_OPSGENIE\n - NOTIFICATION_PROVIDER_SLACK\n - NOTIFICATION_PROVIDER_SMS\n - NOTIFICATION_PROVIDER_TELEGRAM\n - NOTIFICATION_PROVIDER_WEBHOOK\n - NOTIFICATION_PROVIDER_WHATSAPP\n - NOTIFICATION_PROVIDER_MS_TEAMS\n description: NotificationProvider represents the supported notification channel types.\n openstatus.notification.v1.NotificationSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the notification.\n name:\n type: string\n title: name\n description: Display name for the notification channel.\n provider:\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n monitorCount:\n type: integer\n title: monitor_count\n format: int32\n description: Number of monitors associated with this notification.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the notification was created (RFC 3339).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the notification was last updated (RFC 3339).\n title: NotificationSummary\n additionalProperties: false\n description: NotificationSummary represents a notification channel summary for list responses.\n openstatus.notification.v1.NtfyData:\n type: object\n properties:\n topic:\n type: string\n examples:\n - openstatus-alerts\n title: topic\n minLength: 1\n description: Ntfy topic to publish to.\n serverUrl:\n type: string\n title: server_url\n description: Ntfy server URL (defaults to https://ntfy.sh).\n token:\n type:\n - string\n - \"null\"\n title: token\n description: Optional authentication token.\n title: NtfyData\n additionalProperties: false\n description: NtfyData contains configuration for Ntfy notifications.\n openstatus.notification.v1.OpsgenieData:\n type: object\n properties:\n apiKey:\n type: string\n title: api_key\n minLength: 1\n description: Opsgenie API key.\n region:\n title: region\n description: Opsgenie region.\n $ref: '#/components/schemas/openstatus.notification.v1.OpsgenieRegion'\n title: OpsgenieData\n additionalProperties: false\n description: OpsgenieData contains configuration for Opsgenie notifications.\n openstatus.notification.v1.OpsgenieRegion:\n type: string\n title: OpsgenieRegion\n enum:\n - OPSGENIE_REGION_UNSPECIFIED\n - OPSGENIE_REGION_US\n - OPSGENIE_REGION_EU\n description: OpsgenieRegion represents the Opsgenie API region.\n openstatus.notification.v1.PagerDutyData:\n type: object\n properties:\n integrationKey:\n type: string\n examples:\n - a1b2c3d4e5f6g7h8i9j0\n title: integration_key\n minLength: 1\n description: PagerDuty integration key.\n title: PagerDutyData\n additionalProperties: false\n description: PagerDutyData contains configuration for PagerDuty notifications.\n openstatus.notification.v1.SendTestNotificationRequest:\n type: object\n properties:\n provider:\n not:\n enum:\n - NOTIFICATION_PROVIDER_UNSPECIFIED\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n data:\n title: data\n description: Provider-specific configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n title: SendTestNotificationRequest\n required:\n - data\n additionalProperties: false\n description: SendTestNotificationRequest is the request to send a test notification.\n openstatus.notification.v1.SendTestNotificationResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the test was successful.\n errorMessage:\n type:\n - string\n - \"null\"\n title: error_message\n description: Optional error message if the test failed.\n title: SendTestNotificationResponse\n additionalProperties: false\n description: SendTestNotificationResponse is the response after sending a test notification.\n openstatus.notification.v1.SlackData:\n type: object\n properties:\n webhookUrl:\n type: string\n examples:\n - https://hooks.slack.com/services/T00/B00/xxx\n title: webhook_url\n format: uri\n description: Slack webhook URL.\n title: SlackData\n additionalProperties: false\n description: SlackData contains configuration for Slack notifications.\n openstatus.notification.v1.SmsData:\n type: object\n properties:\n phoneNumber:\n type: string\n examples:\n - \"+14155551234\"\n title: phone_number\n minLength: 1\n description: Phone number to send SMS to.\n title: SmsData\n additionalProperties: false\n description: 'Deprecated: SMS is no longer offered; use WhatsappData. Kept for existing channels.'\n deprecated: true\n openstatus.notification.v1.TelegramData:\n type: object\n properties:\n chatId:\n type: string\n examples:\n - \"-1001234567890\"\n title: chat_id\n minLength: 1\n description: Telegram chat ID.\n title: TelegramData\n additionalProperties: false\n description: TelegramData contains configuration for Telegram notifications.\n openstatus.notification.v1.UpdateNotificationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Notification ID to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n description: Updated display name.\n data:\n oneOf:\n - $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n - type: \"null\"\n title: data\n description: Updated provider-specific configuration.\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: Updated monitor IDs to associate.\n updateMonitorIds:\n type:\n - boolean\n - \"null\"\n title: update_monitor_ids\n description: |-\n Set to true to update monitor associations.\n When true, monitor_ids replaces the existing list (empty clears all).\n When false or unset, monitor_ids is ignored and existing associations are preserved.\n title: UpdateNotificationRequest\n additionalProperties: false\n description: UpdateNotificationRequest is the request to update a notification channel.\n openstatus.notification.v1.UpdateNotificationResponse:\n type: object\n properties:\n notification:\n title: notification\n description: The updated notification channel.\n $ref: '#/components/schemas/openstatus.notification.v1.Notification'\n title: UpdateNotificationResponse\n additionalProperties: false\n description: UpdateNotificationResponse is the response after updating a notification channel.\n openstatus.notification.v1.WebhookData:\n type: object\n properties:\n endpoint:\n type: string\n examples:\n - https://api.example.com/webhooks/openstatus\n title: endpoint\n format: uri\n description: Webhook endpoint URL.\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.notification.v1.WebhookHeader'\n title: headers\n description: Optional custom headers.\n title: WebhookData\n additionalProperties: false\n description: WebhookData contains configuration for custom webhook notifications.\n openstatus.notification.v1.WebhookHeader:\n type: object\n properties:\n key:\n type: string\n title: key\n minLength: 1\n description: Header name.\n value:\n type: string\n title: value\n description: Header value.\n title: WebhookHeader\n additionalProperties: false\n description: WebhookHeader represents a custom header for webhook requests.\n openstatus.notification.v1.WhatsappData:\n type: object\n properties:\n phoneNumber:\n type: string\n title: phone_number\n minLength: 1\n description: Phone number to send WhatsApp messages to.\n title: WhatsappData\n additionalProperties: false\n description: WhatsappData contains configuration for WhatsApp notifications.\n openstatus.private_location.v1.CreatePrivateLocationRequest:\n type: object\n properties:\n name:\n type: string\n examples:\n - eu-west-agent\n title: name\n maxLength: 256\n minLength: 1\n description: Display name for the private location (required, 1-256 characters).\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors this private location should run (optional).\n metadata:\n type: object\n title: metadata\n maxProperties: 20\n additionalProperties:\n type: string\n title: value\n maxLength: 256\n description: User-defined key/value labels (optional, up to 20 entries).\n title: CreatePrivateLocationRequest\n additionalProperties: false\n description: CreatePrivateLocationRequest is the request to create a new private location.\n openstatus.private_location.v1.CreatePrivateLocationRequest.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.CreatePrivateLocationResponse:\n type: object\n properties:\n privateLocation:\n title: private_location\n description: The created private location, including its generated agent token.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocation'\n title: CreatePrivateLocationResponse\n additionalProperties: false\n description: CreatePrivateLocationResponse is the response after creating a private location.\n openstatus.private_location.v1.DeletePrivateLocationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the private location to delete (required).\n title: DeletePrivateLocationRequest\n additionalProperties: false\n description: DeletePrivateLocationRequest is the request to delete a private location.\n openstatus.private_location.v1.DeletePrivateLocationResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeletePrivateLocationResponse\n additionalProperties: false\n description: DeletePrivateLocationResponse is the response after deleting a private location.\n openstatus.private_location.v1.GetPrivateLocationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the private location to retrieve (required).\n title: GetPrivateLocationRequest\n additionalProperties: false\n description: GetPrivateLocationRequest is the request to get a private location by ID.\n openstatus.private_location.v1.GetPrivateLocationResponse:\n type: object\n properties:\n privateLocation:\n title: private_location\n description: The requested private location.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocation'\n title: GetPrivateLocationResponse\n additionalProperties: false\n description: GetPrivateLocationResponse is the response containing the private location.\n openstatus.private_location.v1.ListPrivateLocationsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of private locations to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of private locations to skip for pagination (defaults to 0).\n title: ListPrivateLocationsRequest\n additionalProperties: false\n description: ListPrivateLocationsRequest is the request to list private locations.\n openstatus.private_location.v1.ListPrivateLocationsResponse:\n type: object\n properties:\n privateLocations:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocationSummary'\n title: private_locations\n description: List of private locations.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of private locations in the workspace.\n title: ListPrivateLocationsResponse\n additionalProperties: false\n description: ListPrivateLocationsResponse is the response containing private location summaries.\n openstatus.private_location.v1.PrivateLocation:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the private location.\n name:\n type: string\n title: name\n description: Display name for the private location.\n token:\n type: string\n title: token\n description: |-\n Agent credential. Generated by the server on creation and used by the\n agent to authenticate; treat it as a secret.\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors this private location runs.\n lastSeenAt:\n type: string\n title: last_seen_at\n description: Last time the agent reported a result (RFC 3339), empty if it never has.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the private location was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the private location was last updated (RFC 3339 format).\n metadata:\n type: object\n title: metadata\n additionalProperties:\n type: string\n title: value\n description: User-defined key/value labels attached to this private location.\n status:\n title: status\n description: Computed health of the agent. Read-only.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocationStatus'\n title: PrivateLocation\n additionalProperties: false\n description: PrivateLocation represents a self-hosted checker agent with full details.\n openstatus.private_location.v1.PrivateLocation.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.PrivateLocationStatus:\n type: string\n title: PrivateLocationStatus\n enum:\n - PRIVATE_LOCATION_STATUS_UNSPECIFIED\n - PRIVATE_LOCATION_STATUS_ACTIVE\n - PRIVATE_LOCATION_STATUS_ERROR\n description: |-\n PrivateLocationStatus is the computed health of a private location, derived\n from the agent heartbeat. Read-only — it cannot be set through the API.\n openstatus.private_location.v1.PrivateLocationSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the private location.\n name:\n type: string\n title: name\n description: Display name for the private location.\n monitorCount:\n type: integer\n title: monitor_count\n format: int32\n description: Number of monitors this private location runs.\n lastSeenAt:\n type: string\n title: last_seen_at\n description: Last time the agent reported a result (RFC 3339), empty if it never has.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the private location was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the private location was last updated (RFC 3339 format).\n status:\n title: status\n description: Computed health of the agent. Read-only.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocationStatus'\n metadata:\n type: object\n title: metadata\n additionalProperties:\n type: string\n title: value\n description: User-defined key/value labels attached to this private location.\n title: PrivateLocationSummary\n additionalProperties: false\n description: |-\n PrivateLocationSummary represents metadata for a private location (used in\n list responses). The agent token is intentionally omitted.\n openstatus.private_location.v1.PrivateLocationSummary.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.UpdatePrivateLocationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the private location to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n minLength: 1\n description: New display name for the private location (optional).\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: New list of monitor IDs. Only applied when update_monitor_ids is true.\n updateMonitorIds:\n type:\n - boolean\n - \"null\"\n title: update_monitor_ids\n description: |-\n When true, monitor_ids replaces the current associations (an empty list\n clears all). When false or unset, monitor_ids is ignored and existing\n associations are preserved.\n metadata:\n type: object\n title: metadata\n maxProperties: 20\n additionalProperties:\n type: string\n title: value\n maxLength: 256\n description: New key/value labels. Only applied when update_metadata is true.\n updateMetadata:\n type:\n - boolean\n - \"null\"\n title: update_metadata\n description: |-\n When true, metadata replaces the current labels (an empty map clears all).\n When false or unset, metadata is ignored and existing labels are preserved.\n title: UpdatePrivateLocationRequest\n additionalProperties: false\n description: UpdatePrivateLocationRequest is the request to update a private location.\n openstatus.private_location.v1.UpdatePrivateLocationRequest.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.UpdatePrivateLocationResponse:\n type: object\n properties:\n privateLocation:\n title: private_location\n description: The updated private location.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocation'\n title: UpdatePrivateLocationResponse\n additionalProperties: false\n description: UpdatePrivateLocationResponse is the response after updating a private location.\n openstatus.status_page.v1.AddMonitorComponentRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to add the component to (required).\n monitorId:\n type: string\n title: monitor_id\n minLength: 1\n description: ID of the monitor to associate with this component (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n description: Display name for the component (optional, defaults to monitor name).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the component (optional).\n order:\n type:\n - integer\n - \"null\"\n title: order\n format: int32\n description: Display order of the component (optional).\n groupId:\n type:\n - string\n - \"null\"\n title: group_id\n description: ID of the group to add this component to (optional).\n title: AddMonitorComponentRequest\n additionalProperties: false\n description: AddMonitorComponentRequest is the request to add a monitor-based component to a status page.\n openstatus.status_page.v1.AddMonitorComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The created component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: AddMonitorComponentResponse\n additionalProperties: false\n description: AddMonitorComponentResponse is the response after adding a monitor component.\n openstatus.status_page.v1.AddStaticComponentRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to add the component to (required).\n name:\n type: string\n title: name\n maxLength: 256\n minLength: 1\n description: Display name for the component (required).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the component (optional).\n order:\n type:\n - integer\n - \"null\"\n title: order\n format: int32\n description: Display order of the component (optional).\n groupId:\n type:\n - string\n - \"null\"\n title: group_id\n description: ID of the group to add this component to (optional).\n title: AddStaticComponentRequest\n additionalProperties: false\n description: AddStaticComponentRequest is the request to add a static component to a status page.\n openstatus.status_page.v1.AddStaticComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The created component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: AddStaticComponentResponse\n additionalProperties: false\n description: AddStaticComponentResponse is the response after adding a static component.\n openstatus.status_page.v1.ComponentDayBucket:\n type: object\n properties:\n day:\n type: string\n title: day\n description: Day in RFC 3339 format (UTC midnight).\n count:\n type:\n - integer\n - string\n title: count\n format: int64\n description: Total checks that day.\n ok:\n type:\n - integer\n - string\n title: ok\n format: int64\n description: Successful checks.\n degraded:\n type:\n - integer\n - string\n title: degraded\n format: int64\n description: Degraded checks.\n error:\n type:\n - integer\n - string\n title: error\n format: int64\n description: Failed checks.\n status:\n title: status\n description: Convenience resolved status for the day.\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentDayStatus'\n impact:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_report.v1.PageComponentImpact'\n - type: \"null\"\n title: impact\n description: Worst status-report impact overlapping the day (absent when none).\n title: ComponentDayBucket\n additionalProperties: false\n description: ComponentDayBucket is one day of status data for a component.\n openstatus.status_page.v1.ComponentDayStatus:\n type: string\n title: ComponentDayStatus\n enum:\n - COMPONENT_DAY_STATUS_UNSPECIFIED\n - COMPONENT_DAY_STATUS_OPERATIONAL\n - COMPONENT_DAY_STATUS_DEGRADED\n - COMPONENT_DAY_STATUS_DOWN\n - COMPONENT_DAY_STATUS_MAINTENANCE\n - COMPONENT_DAY_STATUS_EMPTY\n description: ComponentDayStatus is the resolved status of a component on a given day.\n openstatus.status_page.v1.ComponentEvent:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Identifier of the underlying event.\n name:\n type: string\n title: name\n description: Human-readable name (incident \"Downtime\", maintenance / report title).\n type:\n title: type\n description: Kind of event.\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentEventType'\n status:\n title: status\n description: Projected status this event contributes.\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentEventStatus'\n from:\n type: string\n title: from\n description: Start time (RFC 3339).\n to:\n type:\n - string\n - \"null\"\n title: to\n description: End time (RFC 3339); absent while the event is ongoing.\n impact:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_report.v1.PageComponentImpact'\n - type: \"null\"\n title: impact\n description: Worst impact over the event (reports only; absent otherwise).\n title: ComponentEvent\n additionalProperties: false\n description: ComponentEvent is a single incident / maintenance / report affecting a component.\n openstatus.status_page.v1.ComponentEventStatus:\n type: string\n title: ComponentEventStatus\n enum:\n - COMPONENT_EVENT_STATUS_UNSPECIFIED\n - COMPONENT_EVENT_STATUS_OPERATIONAL\n - COMPONENT_EVENT_STATUS_DEGRADED\n - COMPONENT_EVENT_STATUS_DOWN\n - COMPONENT_EVENT_STATUS_MAINTENANCE\n description: ComponentEventStatus is the projected status an event contributes.\n openstatus.status_page.v1.ComponentEventType:\n type: string\n title: ComponentEventType\n enum:\n - COMPONENT_EVENT_TYPE_UNSPECIFIED\n - COMPONENT_EVENT_TYPE_MAINTENANCE\n - COMPONENT_EVENT_TYPE_INCIDENT\n - COMPONENT_EVENT_TYPE_REPORT\n description: ComponentEventType is the kind of timeline event affecting a component.\n openstatus.status_page.v1.ComponentStatus:\n type: object\n properties:\n componentId:\n type: string\n title: component_id\n description: ID of the component.\n status:\n title: status\n description: Current status of the component.\n $ref: '#/components/schemas/openstatus.status_page.v1.OverallStatus'\n title: ComponentStatus\n additionalProperties: false\n description: ComponentStatus represents the status of a single component.\n openstatus.status_page.v1.CreateComponentGroupRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to create the group in (required).\n name:\n type: string\n title: name\n maxLength: 256\n minLength: 1\n description: Display name for the group (required).\n defaultOpen:\n type:\n - boolean\n - \"null\"\n title: default_open\n description: Whether the group should be expanded by default on the status page (optional, defaults to false).\n title: CreateComponentGroupRequest\n additionalProperties: false\n description: CreateComponentGroupRequest is the request to create a new component group.\n openstatus.status_page.v1.CreateComponentGroupResponse:\n type: object\n properties:\n group:\n title: group\n description: The created component group.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: CreateComponentGroupResponse\n additionalProperties: false\n description: CreateComponentGroupResponse is the response after creating a component group.\n openstatus.status_page.v1.CreatePageSubscriptionRequest:\n type: object\n allOf:\n - properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 255\n description: Optional human-readable label.\n componentIds:\n type: array\n items:\n type: string\n title: component_ids\n description: Component scope. Empty = entire page.\n - oneOf:\n - type: object\n properties:\n emailChannel:\n title: email_channel\n $ref: '#/components/schemas/openstatus.status_page.v1.EmailChannel'\n title: email_channel\n required:\n - emailChannel\n - type: object\n properties:\n webhookChannel:\n title: webhook_channel\n $ref: '#/components/schemas/openstatus.status_page.v1.WebhookChannel'\n title: webhook_channel\n required:\n - webhookChannel\n title: CreatePageSubscriptionRequest\n additionalProperties: false\n description: CreatePageSubscriptionRequest is the request to add a vendor-managed subscriber to a status page.\n openstatus.status_page.v1.CreatePageSubscriptionResponse:\n type: object\n properties:\n subscriber:\n title: subscriber\n description: The created subscriber.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageSubscriber'\n title: CreatePageSubscriptionResponse\n additionalProperties: false\n description: CreatePageSubscriptionResponse is the response after creating a vendor-managed subscription.\n openstatus.status_page.v1.CreateStatusPageRequest:\n type: object\n properties:\n title:\n type: string\n examples:\n - Acme Corp Status\n title: title\n maxLength: 256\n minLength: 1\n description: Title of the status page (required).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the status page (optional).\n slug:\n type: string\n examples:\n - my-status-page\n title: slug\n maxLength: 256\n minLength: 1\n pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$\n description: URL-friendly slug for the status page (required). Must be lowercase alphanumeric with hyphens.\n homepageUrl:\n type:\n - string\n - \"null\"\n examples:\n - https://www.example.com\n title: homepage_url\n description: URL to the homepage (optional).\n contactUrl:\n type:\n - string\n - \"null\"\n title: contact_url\n description: URL to the contact page (optional).\n defaultLocale:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n - type: \"null\"\n title: default_locale\n description: Default locale for the status page (optional, defaults to EN).\n locales:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n title: locales\n description: Enabled locales for the status page (optional).\n icon:\n type:\n - string\n - \"null\"\n title: icon\n maxLength: 1024\n description: Icon URL for the status page (optional).\n customDomain:\n type:\n - string\n - \"null\"\n title: custom_domain\n maxLength: 256\n description: Custom domain for the status page (optional).\n theme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageTheme'\n - type: \"null\"\n title: theme\n description: Visual theme for the status page (optional, defaults to SYSTEM).\n accessType:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageAccessType'\n - type: \"null\"\n title: access_type\n description: Access type for the status page (optional, defaults to PUBLIC).\n password:\n type:\n - string\n - \"null\"\n title: password\n maxLength: 256\n minLength: 1\n description: Password for the status page (required when access_type is PASSWORD_PROTECTED).\n authEmailDomains:\n type: array\n items:\n type: string\n title: auth_email_domains\n description: Email domains allowed to access the page (used when access_type is AUTHENTICATED).\n allowIndex:\n type:\n - boolean\n - \"null\"\n title: allow_index\n description: Whether search engines are allowed to index this status page (optional, defaults to true).\n allowedIpRanges:\n type:\n - string\n - \"null\"\n title: allowed_ip_ranges\n description: Comma-separated IPv4 CIDR ranges (required when access_type is IP_RESTRICTED).\n customTheme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.CustomTheme'\n - type: \"null\"\n title: custom_theme\n description: |-\n Per-mode CSS variable overrides merged over the theme (optional).\n Only supported variable names are accepted. Requires the custom-theme plan feature.\n title: CreateStatusPageRequest\n additionalProperties: false\n description: CreateStatusPageRequest is the request to create a new status page.\n openstatus.status_page.v1.CreateStatusPageResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The created status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n title: CreateStatusPageResponse\n additionalProperties: false\n description: CreateStatusPageResponse is the response after creating a status page.\n openstatus.status_page.v1.CustomTheme:\n type: object\n properties:\n light:\n type: object\n title: light\n additionalProperties:\n type: string\n title: value\n description: 'CSS variable overrides applied in light mode, keyed by variable name (e.g. \"--primary\": \"hsl(24 94% 50%)\").'\n dark:\n type: object\n title: dark\n additionalProperties:\n type: string\n title: value\n description: CSS variable overrides applied in dark mode, keyed by variable name.\n title: CustomTheme\n additionalProperties: false\n description: CustomTheme holds per-mode CSS variable overrides merged over the page theme.\n openstatus.status_page.v1.CustomTheme.DarkEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: DarkEntry\n additionalProperties: false\n openstatus.status_page.v1.CustomTheme.LightEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: LightEntry\n additionalProperties: false\n openstatus.status_page.v1.DeleteComponentGroupRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component group to delete (required).\n title: DeleteComponentGroupRequest\n additionalProperties: false\n description: DeleteComponentGroupRequest is the request to delete a component group.\n openstatus.status_page.v1.DeleteComponentGroupResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteComponentGroupResponse\n additionalProperties: false\n description: DeleteComponentGroupResponse is the response after deleting a component group.\n openstatus.status_page.v1.DeleteStatusPageRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page to delete (required).\n title: DeleteStatusPageRequest\n additionalProperties: false\n description: DeleteStatusPageRequest is the request to delete a status page.\n openstatus.status_page.v1.DeleteStatusPageResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteStatusPageResponse\n additionalProperties: false\n description: DeleteStatusPageResponse is the response after deleting a status page.\n openstatus.status_page.v1.EmailChannel:\n type: object\n properties:\n email:\n type: string\n title: email\n format: email\n description: Email address of the subscriber (required).\n title: EmailChannel\n additionalProperties: false\n description: EmailChannel carries the email-channel fields for CreatePageSubscription.\n openstatus.status_page.v1.GetOverallStatusRequest:\n type: object\n oneOf:\n - type: object\n properties:\n id:\n type: string\n title: id\n description: ID of the status page.\n title: id\n required:\n - id\n - type: object\n properties:\n slug:\n type: string\n title: slug\n description: Slug of the status page.\n title: slug\n required:\n - slug\n title: GetOverallStatusRequest\n additionalProperties: false\n description: GetOverallStatusRequest is the request to get the overall status of a status page.\n openstatus.status_page.v1.GetOverallStatusResponse:\n type: object\n properties:\n overallStatus:\n title: overall_status\n description: Aggregated status across all components.\n $ref: '#/components/schemas/openstatus.status_page.v1.OverallStatus'\n componentStatuses:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentStatus'\n title: component_statuses\n description: Status of individual components.\n title: GetOverallStatusResponse\n additionalProperties: false\n description: GetOverallStatusResponse is the response containing the overall status and individual component statuses.\n openstatus.status_page.v1.GetPageComponentDailySummaryRequest:\n type: object\n allOf:\n - properties:\n componentIds:\n type: array\n items:\n type: string\n minLength: 1\n title: component_ids\n description: Restrict the result to these component ids (optional, defaults to all components).\n days:\n type:\n - integer\n - \"null\"\n title: days\n maximum: 45\n minimum: 1\n format: int32\n description: Number of days to return (1-45, defaults to 45 if unspecified).\n - oneOf:\n - type: object\n properties:\n id:\n type: string\n title: id\n description: ID of the status page.\n title: id\n required:\n - id\n - type: object\n properties:\n slug:\n type: string\n title: slug\n description: Slug of the status page.\n title: slug\n required:\n - slug\n title: GetPageComponentDailySummaryRequest\n additionalProperties: false\n description: GetPageComponentDailySummaryRequest is the request for per-component daily summaries.\n openstatus.status_page.v1.GetPageComponentDailySummaryResponse:\n type: object\n properties:\n components:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentDailySummary'\n title: components\n description: Per-component daily summaries, in page order.\n title: GetPageComponentDailySummaryResponse\n additionalProperties: false\n description: GetPageComponentDailySummaryResponse is the response containing per-component summaries.\n openstatus.status_page.v1.GetPageComponentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component to retrieve (required).\n title: GetPageComponentRequest\n additionalProperties: false\n description: GetPageComponentRequest is the request to fetch a single component by ID.\n openstatus.status_page.v1.GetPageComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The requested component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: GetPageComponentResponse\n additionalProperties: false\n description: GetPageComponentResponse is the response containing the component.\n openstatus.status_page.v1.GetStatusPageContentRequest:\n type: object\n oneOf:\n - type: object\n properties:\n id:\n type: string\n title: id\n description: ID of the status page.\n title: id\n required:\n - id\n - type: object\n properties:\n slug:\n type: string\n title: slug\n description: Slug of the status page.\n title: slug\n required:\n - slug\n title: GetStatusPageContentRequest\n additionalProperties: false\n description: GetStatusPageContentRequest is the request to get the full content of a status page.\n openstatus.status_page.v1.GetStatusPageContentResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The status page details.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n components:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: components\n description: Components on the status page.\n groups:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: groups\n description: Component groups on the status page.\n statusReports:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: status_reports\n description: Active and recent status reports.\n maintenances:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary'\n title: maintenances\n description: Scheduled maintenances.\n title: GetStatusPageContentResponse\n additionalProperties: false\n description: GetStatusPageContentResponse is the response containing the full status page content.\n openstatus.status_page.v1.GetStatusPageOverviewRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page (required, workspace-scoped).\n title: GetStatusPageOverviewRequest\n additionalProperties: false\n description: GetStatusPageOverviewRequest requests the full overview of a status page by id.\n openstatus.status_page.v1.GetStatusPageOverviewResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The status page details.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n configuration:\n title: configuration\n description: Rich rendering configuration of the page.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageConfiguration'\n components:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: components\n description: Components on the status page.\n groups:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: groups\n description: Component groups on the status page.\n statusReports:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: status_reports\n description: Active and recent status reports.\n maintenances:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary'\n title: maintenances\n description: Scheduled maintenances.\n overallStatus:\n title: overall_status\n description: Aggregated status across all components.\n $ref: '#/components/schemas/openstatus.status_page.v1.OverallStatus'\n componentStatuses:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentStatus'\n title: component_statuses\n description: Status of individual components.\n title: GetStatusPageOverviewResponse\n additionalProperties: false\n description: GetStatusPageOverviewResponse bundles all data for a single status page.\n openstatus.status_page.v1.GetStatusPageRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page to retrieve (required).\n title: GetStatusPageRequest\n additionalProperties: false\n description: GetStatusPageRequest is the request to get a status page by ID.\n openstatus.status_page.v1.GetStatusPageResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The requested status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n title: GetStatusPageResponse\n additionalProperties: false\n description: GetStatusPageResponse is the response containing the status page.\n openstatus.status_page.v1.ListStatusPagesRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of pages to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of pages to skip for pagination (defaults to 0).\n title: ListStatusPagesRequest\n additionalProperties: false\n description: ListStatusPagesRequest is the request to list status pages.\n openstatus.status_page.v1.ListStatusPagesResponse:\n type: object\n properties:\n statusPages:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPageSummary'\n title: status_pages\n description: List of status pages (metadata only).\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of status pages.\n title: ListStatusPagesResponse\n additionalProperties: false\n description: ListStatusPagesResponse is the response containing status page summaries.\n openstatus.status_page.v1.ListSubscribersRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to list subscribers for (required).\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of subscribers to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of subscribers to skip for pagination (defaults to 0).\n includeUnsubscribed:\n type:\n - boolean\n - \"null\"\n title: include_unsubscribed\n description: Whether to include unsubscribed users (defaults to false).\n title: ListSubscribersRequest\n additionalProperties: false\n description: ListSubscribersRequest is the request to list subscribers of a status page.\n openstatus.status_page.v1.ListSubscribersResponse:\n type: object\n properties:\n subscribers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageSubscriber'\n title: subscribers\n description: List of subscribers.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of subscribers matching the filter.\n title: ListSubscribersResponse\n additionalProperties: false\n description: ListSubscribersResponse is the response containing status page subscribers.\n openstatus.status_page.v1.Locale:\n type: string\n title: Locale\n enum:\n - LOCALE_UNSPECIFIED\n - LOCALE_EN\n - LOCALE_FR\n - LOCALE_DE\n - LOCALE_TR\n - LOCALE_HI\n - LOCALE_KO\n - LOCALE_JA\n description: Locale defines the supported languages for a status page.\n openstatus.status_page.v1.OverallStatus:\n type: string\n title: OverallStatus\n enum:\n - OVERALL_STATUS_UNSPECIFIED\n - OVERALL_STATUS_OPERATIONAL\n - OVERALL_STATUS_DEGRADED\n - OVERALL_STATUS_PARTIAL_OUTAGE\n - OVERALL_STATUS_MAJOR_OUTAGE\n - OVERALL_STATUS_MAINTENANCE\n - OVERALL_STATUS_UNKNOWN\n description: OverallStatus represents the aggregated status of all components on a page.\n openstatus.status_page.v1.PageAccessType:\n type: string\n title: PageAccessType\n enum:\n - PAGE_ACCESS_TYPE_UNSPECIFIED\n - PAGE_ACCESS_TYPE_PUBLIC\n - PAGE_ACCESS_TYPE_PASSWORD_PROTECTED\n - PAGE_ACCESS_TYPE_AUTHENTICATED\n - PAGE_ACCESS_TYPE_IP_RESTRICTED\n description: PageAccessType defines who can access the status page.\n openstatus.status_page.v1.PageBarType:\n type: string\n title: PageBarType\n enum:\n - PAGE_BAR_TYPE_UNSPECIFIED\n - PAGE_BAR_TYPE_ABSOLUTE\n - PAGE_BAR_TYPE_MANUAL\n description: PageBarType mirrors page.configuration.type (how the status bar is computed).\n openstatus.status_page.v1.PageComponent:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the component.\n pageId:\n type: string\n title: page_id\n description: ID of the status page this component belongs to.\n name:\n type: string\n title: name\n description: Display name of the component.\n description:\n type: string\n title: description\n description: Description of the component (optional).\n type:\n title: type\n description: Type of the component (monitor or static).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentType'\n monitorId:\n type: string\n title: monitor_id\n description: ID of the monitor if type is MONITOR (optional).\n order:\n type: integer\n title: order\n format: int32\n description: Display order of the component.\n groupId:\n type: string\n title: group_id\n description: ID of the group this component belongs to (optional).\n groupOrder:\n type: integer\n title: group_order\n format: int32\n description: Order within the group if grouped.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the component was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the component was last updated (RFC 3339 format).\n title: PageComponent\n additionalProperties: false\n description: PageComponent represents a component displayed on a status page.\n openstatus.status_page.v1.PageComponentDailySummary:\n type: object\n properties:\n componentId:\n type: string\n title: component_id\n description: ID of the component.\n type:\n title: type\n description: Component type (monitor or static).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentType'\n monitorId:\n type:\n - string\n - \"null\"\n title: monitor_id\n description: Monitor id backing the component (absent for static components).\n name:\n type: string\n title: name\n description: Display name of the component.\n buckets:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentDayBucket'\n title: buckets\n description: Per-day buckets, oldest first.\n events:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentEvent'\n title: events\n description: Events affecting the component within the window.\n title: PageComponentDailySummary\n additionalProperties: false\n description: PageComponentDailySummary is the per-day buckets + event timeline for one component.\n openstatus.status_page.v1.PageComponentGroup:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the group.\n pageId:\n type: string\n title: page_id\n description: ID of the status page this group belongs to.\n name:\n type: string\n title: name\n description: Display name of the group.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the group was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the group was last updated (RFC 3339 format).\n defaultOpen:\n type: boolean\n title: default_open\n description: Whether the group should be expanded by default on the status page.\n title: PageComponentGroup\n additionalProperties: false\n description: PageComponentGroup represents a group of components on a status page.\n openstatus.status_page.v1.PageComponentType:\n type: string\n title: PageComponentType\n enum:\n - PAGE_COMPONENT_TYPE_UNSPECIFIED\n - PAGE_COMPONENT_TYPE_MONITOR\n - PAGE_COMPONENT_TYPE_STATIC\n description: PageComponentType defines the type of a component on a status page.\n openstatus.status_page.v1.PageConfiguration:\n type: object\n properties:\n metricType:\n title: metric_type\n description: Which metric the status bars represent (configuration.value).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageMetricType'\n barType:\n title: bar_type\n description: How the status bar is computed (configuration.type).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageBarType'\n showUptime:\n type: boolean\n title: show_uptime\n description: Whether to show the uptime percentage (configuration.uptime).\n themeKey:\n type: string\n title: theme_key\n description: |-\n Theme key from the theme store (configuration.theme), e.g. \"default\".\n Free-form string rather than an enum because the theme catalog is dynamic.\n days:\n type: integer\n title: days\n format: int32\n description: 'Number of uptime-bar days rendered on the page (configuration.days): 30 or 45.'\n title: PageConfiguration\n additionalProperties: false\n description: PageConfiguration is the rich rendering config stored in page.configuration.\n openstatus.status_page.v1.PageMetricType:\n type: string\n title: PageMetricType\n enum:\n - PAGE_METRIC_TYPE_UNSPECIFIED\n - PAGE_METRIC_TYPE_DURATION\n - PAGE_METRIC_TYPE_REQUESTS\n - PAGE_METRIC_TYPE_MANUAL\n description: PageMetricType mirrors page.configuration.value (which metric the status bars represent).\n openstatus.status_page.v1.PageSubscriber:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the subscriber.\n pageId:\n type: string\n title: page_id\n description: ID of the status page the user is subscribed to.\n email:\n type: string\n title: email\n description: Email address of the subscriber (empty for webhook channels).\n acceptedAt:\n type: string\n title: accepted_at\n description: Timestamp when the subscription was accepted/confirmed (RFC 3339 format, optional).\n unsubscribedAt:\n type: string\n title: unsubscribed_at\n description: Timestamp when the user unsubscribed (RFC 3339 format, optional).\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the subscription was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the subscription was last updated (RFC 3339 format).\n source:\n title: source\n description: How the subscription was created. Vendor-added rows skip verification.\n $ref: '#/components/schemas/openstatus.status_page.v1.SubscriberSource'\n name:\n type:\n - string\n - \"null\"\n title: name\n description: 'Optional human-readable label (e.g. \"Supabase #incidents\").'\n channelType:\n type: string\n title: channel_type\n description: 'Channel type: \"email\" or \"webhook\".'\n webhookUrl:\n type:\n - string\n - \"null\"\n title: webhook_url\n description: Webhook URL (populated only for channel_type = \"webhook\").\n channelConfig:\n type:\n - string\n - \"null\"\n title: channel_config\n description: JSON-encoded channel config (e.g. custom headers). Populated for webhook channels.\n componentIds:\n type: array\n items:\n type: string\n title: component_ids\n description: IDs of components this subscription is scoped to. Empty = entire page.\n title: PageSubscriber\n additionalProperties: false\n description: PageSubscriber represents a subscriber to a status page.\n openstatus.status_page.v1.PageTheme:\n type: string\n title: PageTheme\n enum:\n - PAGE_THEME_UNSPECIFIED\n - PAGE_THEME_SYSTEM\n - PAGE_THEME_LIGHT\n - PAGE_THEME_DARK\n description: PageTheme defines the visual theme of the status page.\n openstatus.status_page.v1.RemoveComponentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component to remove (required).\n title: RemoveComponentRequest\n additionalProperties: false\n description: RemoveComponentRequest is the request to remove a component from a status page.\n openstatus.status_page.v1.RemoveComponentResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the removal was successful.\n title: RemoveComponentResponse\n additionalProperties: false\n description: RemoveComponentResponse is the response after removing a component.\n openstatus.status_page.v1.StatusPage:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status page.\n title:\n type: string\n title: title\n description: Title of the status page.\n description:\n type: string\n title: description\n description: Description of the status page.\n slug:\n type: string\n examples:\n - acme-corp\n title: slug\n description: URL-friendly slug for the status page.\n customDomain:\n type: string\n examples:\n - status.example.com\n title: custom_domain\n description: Custom domain for the status page (optional).\n published:\n type: boolean\n title: published\n description: Whether the status page is published and visible.\n accessType:\n title: access_type\n description: Access type for the status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageAccessType'\n theme:\n title: theme\n description: Visual theme for the status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageTheme'\n homepageUrl:\n type: string\n title: homepage_url\n description: URL to the homepage (optional).\n contactUrl:\n type: string\n title: contact_url\n description: URL to the contact page (optional).\n icon:\n type: string\n title: icon\n description: Icon URL for the status page (optional).\n createdAt:\n type: string\n examples:\n - \"2024-01-15T09:00:00Z\"\n title: created_at\n description: Timestamp when the page was created (RFC 3339 format).\n updatedAt:\n type: string\n examples:\n - \"2024-06-20T14:30:00Z\"\n title: updated_at\n description: Timestamp when the page was last updated (RFC 3339 format).\n defaultLocale:\n title: default_locale\n description: Default locale for the status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n locales:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n title: locales\n description: Enabled locales for the status page.\n password:\n type: string\n title: password\n description: Password for the status page (only set when access_type is PASSWORD_PROTECTED).\n authEmailDomains:\n type: array\n items:\n type: string\n title: auth_email_domains\n description: Email domains allowed to access the page (only set when access_type is AUTHENTICATED).\n allowIndex:\n type: boolean\n title: allow_index\n description: Whether search engines are allowed to index this status page.\n allowedIpRanges:\n type: string\n title: allowed_ip_ranges\n description: Comma-separated IPv4 CIDR ranges (only set when access_type is IP_RESTRICTED).\n customTheme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.CustomTheme'\n - type: \"null\"\n title: custom_theme\n description: Per-mode CSS variable overrides merged over the theme (only set when configured).\n title: StatusPage\n additionalProperties: false\n description: StatusPage represents a full status page with all details.\n openstatus.status_page.v1.StatusPageSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status page.\n title:\n type: string\n title: title\n description: Title of the status page.\n slug:\n type: string\n title: slug\n description: URL-friendly slug for the status page.\n published:\n type: boolean\n title: published\n description: Whether the status page is published and visible.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the page was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the page was last updated (RFC 3339 format).\n customDomain:\n type: string\n examples:\n - status.example.com\n title: custom_domain\n description: Custom domain for the status page (optional).\n title: StatusPageSummary\n additionalProperties: false\n description: StatusPageSummary represents metadata for a status page (used in list responses).\n openstatus.status_page.v1.SubscribeToPageRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to subscribe to (required).\n email:\n type: string\n examples:\n - user@example.com\n title: email\n format: email\n description: Email address to subscribe (required).\n title: SubscribeToPageRequest\n additionalProperties: false\n description: SubscribeToPageRequest is the request to subscribe an email to a status page.\n openstatus.status_page.v1.SubscribeToPageResponse:\n type: object\n properties:\n subscriber:\n title: subscriber\n description: The created subscriber.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageSubscriber'\n title: SubscribeToPageResponse\n additionalProperties: false\n description: SubscribeToPageResponse is the response after subscribing to a status page.\n openstatus.status_page.v1.SubscriberSource:\n type: string\n title: SubscriberSource\n enum:\n - SUBSCRIBER_SOURCE_UNSPECIFIED\n - SUBSCRIBER_SOURCE_SELF_SIGNUP\n - SUBSCRIBER_SOURCE_VENDOR\n - SUBSCRIBER_SOURCE_IMPORT\n description: SubscriberSource indicates how the subscription was created.\n openstatus.status_page.v1.UnsubscribeFromPageRequest:\n type: object\n allOf:\n - properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to unsubscribe from (required).\n - oneOf:\n - type: object\n properties:\n email:\n type: string\n title: email\n description: Email address to unsubscribe.\n title: email\n required:\n - email\n - type: object\n properties:\n id:\n type: string\n title: id\n description: Subscriber ID.\n title: id\n required:\n - id\n title: UnsubscribeFromPageRequest\n additionalProperties: false\n description: UnsubscribeFromPageRequest is the request to unsubscribe from a status page.\n openstatus.status_page.v1.UnsubscribeFromPageResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the unsubscription was successful.\n title: UnsubscribeFromPageResponse\n additionalProperties: false\n description: UnsubscribeFromPageResponse is the response after unsubscribing from a status page.\n openstatus.status_page.v1.UpdateComponentGroupRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component group to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n minLength: 1\n description: New display name for the group (optional).\n defaultOpen:\n type:\n - boolean\n - \"null\"\n title: default_open\n description: Whether the group should be expanded by default on the status page (optional).\n title: UpdateComponentGroupRequest\n additionalProperties: false\n description: UpdateComponentGroupRequest is the request to update a component group.\n openstatus.status_page.v1.UpdateComponentGroupResponse:\n type: object\n properties:\n group:\n title: group\n description: The updated component group.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: UpdateComponentGroupResponse\n additionalProperties: false\n description: UpdateComponentGroupResponse is the response after updating a component group.\n openstatus.status_page.v1.UpdateComponentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n description: New display name for the component (optional).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: New description for the component (optional).\n order:\n type:\n - integer\n - \"null\"\n title: order\n format: int32\n description: New display order (optional).\n groupId:\n type:\n - string\n - \"null\"\n title: group_id\n description: New group ID (optional, set to empty string to remove from group).\n groupOrder:\n type:\n - integer\n - \"null\"\n title: group_order\n format: int32\n description: New order within the group (optional).\n title: UpdateComponentRequest\n additionalProperties: false\n description: UpdateComponentRequest is the request to update a component.\n openstatus.status_page.v1.UpdateComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The updated component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: UpdateComponentResponse\n additionalProperties: false\n description: UpdateComponentResponse is the response after updating a component.\n openstatus.status_page.v1.UpdateStatusPageRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page to update (required).\n title:\n type:\n - string\n - \"null\"\n title: title\n maxLength: 256\n minLength: 1\n description: New title for the status page (optional).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: New description for the status page (optional).\n slug:\n type:\n - string\n - \"null\"\n title: slug\n maxLength: 256\n minLength: 1\n pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$\n description: New slug for the status page (optional).\n homepageUrl:\n type:\n - string\n - \"null\"\n title: homepage_url\n description: New homepage URL (optional).\n contactUrl:\n type:\n - string\n - \"null\"\n title: contact_url\n description: New contact URL (optional).\n defaultLocale:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n - type: \"null\"\n title: default_locale\n description: New default locale for the status page (optional).\n locales:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n title: locales\n description: New enabled locales for the status page (optional).\n icon:\n type:\n - string\n - \"null\"\n title: icon\n maxLength: 1024\n description: New icon URL for the status page (optional).\n customDomain:\n type:\n - string\n - \"null\"\n title: custom_domain\n maxLength: 256\n description: New custom domain (optional).\n theme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageTheme'\n - type: \"null\"\n title: theme\n description: New visual theme (optional).\n accessType:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageAccessType'\n - type: \"null\"\n title: access_type\n description: New access type (optional).\n password:\n type:\n - string\n - \"null\"\n title: password\n maxLength: 256\n minLength: 1\n description: New password (optional, required when access_type is PASSWORD_PROTECTED).\n authEmailDomains:\n type: array\n items:\n type: string\n title: auth_email_domains\n description: New email domains (optional, required when access_type is AUTHENTICATED).\n allowIndex:\n type:\n - boolean\n - \"null\"\n title: allow_index\n description: Whether search engines are allowed to index this status page (optional).\n allowedIpRanges:\n type:\n - string\n - \"null\"\n title: allowed_ip_ranges\n description: Comma-separated IPv4 CIDR ranges (required when access_type is IP_RESTRICTED).\n customTheme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.CustomTheme'\n - type: \"null\"\n title: custom_theme\n description: |-\n New per-mode CSS variable overrides (optional). Omit to keep the current\n value; send an empty message to clear. Requires the custom-theme plan feature.\n title: UpdateStatusPageRequest\n additionalProperties: false\n description: UpdateStatusPageRequest is the request to update a status page.\n openstatus.status_page.v1.UpdateStatusPageResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The updated status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n title: UpdateStatusPageResponse\n additionalProperties: false\n description: UpdateStatusPageResponse is the response after updating a status page.\n openstatus.status_page.v1.WebhookChannel:\n type: object\n properties:\n webhookUrl:\n type: string\n title: webhook_url\n maxLength: 2048\n minLength: 1\n format: uri\n description: Webhook URL (required). Payload flavor (Slack / Discord / generic) is auto-detected from the URL prefix.\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.WebhookChannelHeader'\n title: headers\n description: Optional custom HTTP headers attached to every dispatch.\n title: WebhookChannel\n additionalProperties: false\n description: WebhookChannel carries the webhook-channel fields for CreatePageSubscription.\n openstatus.status_page.v1.WebhookChannelHeader:\n type: object\n properties:\n key:\n type: string\n title: key\n minLength: 1\n value:\n type: string\n title: value\n title: WebhookChannelHeader\n additionalProperties: false\n description: WebhookChannelHeader is a single custom HTTP header.\n openstatus.status_report.v1.AddStatusReportUpdateRequest:\n type: object\n properties:\n statusReportId:\n type: string\n title: status_report_id\n minLength: 1\n description: ID of the status report to update (required).\n status:\n title: status\n description: New status for the report (required).\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n message:\n type: string\n title: message\n minLength: 1\n description: Message describing what changed (required).\n date:\n type:\n - string\n - \"null\"\n title: date\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: Optional date for the update (RFC 3339 format). Defaults to current time if not provided.\n notify:\n type:\n - boolean\n - \"null\"\n title: notify\n description: Whether to notify subscribers about this update (optional, defaults to false).\n componentImpacts:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.ComponentImpact'\n title: component_impacts\n description: |-\n Per-component impacts this update sets (optional). Components named here\n are added to the report's affected set; omitted components keep their\n prior impact.\n title: AddStatusReportUpdateRequest\n additionalProperties: false\n description: AddStatusReportUpdateRequest is the request to add a new update to a status report.\n openstatus.status_report.v1.AddStatusReportUpdateResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The updated status report with the new update included.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: AddStatusReportUpdateResponse\n additionalProperties: false\n description: AddStatusReportUpdateResponse is the response after adding an update to a status report.\n openstatus.status_report.v1.ComponentImpact:\n type: object\n properties:\n pageComponentId:\n type: string\n title: page_component_id\n description: ID of the affected page component.\n impact:\n title: impact\n description: Impact set for the component.\n $ref: '#/components/schemas/openstatus.status_report.v1.PageComponentImpact'\n title: ComponentImpact\n additionalProperties: false\n description: ComponentImpact pairs a page component with the impact an update set for it.\n openstatus.status_report.v1.CreateStatusReportRequest:\n type: object\n properties:\n title:\n type: string\n examples:\n - API Degradation Investigation\n title: title\n minLength: 1\n description: Title of the status report (required).\n status:\n title: status\n description: Initial status (required).\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n message:\n type: string\n examples:\n - We are investigating reports of increased API latency.\n title: message\n minLength: 1\n description: Initial message describing the incident (required).\n date:\n type: string\n examples:\n - \"2024-03-15T10:30:00Z\"\n title: date\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: Date when the event occurred (RFC 3339 format, required).\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: Page ID to associate with this report (required).\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: Page component IDs to associate with this report (optional).\n notify:\n type:\n - boolean\n - \"null\"\n title: notify\n description: Whether to notify subscribers about this status report (optional, defaults to false).\n componentImpacts:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.ComponentImpact'\n title: component_impacts\n description: |-\n Per-component impacts set by the initial update (optional). When provided,\n the named components are added to the report's affected set. Omitting this\n field creates a legacy report without impact tracking.\n title: CreateStatusReportRequest\n additionalProperties: false\n description: CreateStatusReportRequest is the request to create a new status report.\n openstatus.status_report.v1.CreateStatusReportResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The created status report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: CreateStatusReportResponse\n additionalProperties: false\n description: CreateStatusReportResponse is the response after creating a status report.\n openstatus.status_report.v1.DeleteStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status report to delete (required).\n title: DeleteStatusReportRequest\n additionalProperties: false\n description: DeleteStatusReportRequest is the request to delete a status report.\n openstatus.status_report.v1.DeleteStatusReportResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteStatusReportResponse\n additionalProperties: false\n description: DeleteStatusReportResponse is the response after deleting a status report.\n openstatus.status_report.v1.GetStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status report to retrieve (required).\n title: GetStatusReportRequest\n additionalProperties: false\n description: GetStatusReportRequest is the request to get a status report by ID.\n openstatus.status_report.v1.GetStatusReportResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The requested status report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: GetStatusReportResponse\n additionalProperties: false\n description: GetStatusReportResponse is the response containing the status report with its full update timeline.\n openstatus.status_report.v1.ListStatusReportsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of reports to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of reports to skip for pagination (defaults to 0).\n statuses:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n title: statuses\n description: Filter by status (optional). If empty, returns all statuses.\n title: ListStatusReportsRequest\n additionalProperties: false\n description: ListStatusReportsRequest is the request to list status reports.\n openstatus.status_report.v1.ListStatusReportsResponse:\n type: object\n properties:\n statusReports:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportSummary'\n title: status_reports\n description: List of status reports (metadata only, use GetStatusReport for full details).\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of reports matching the filter.\n title: ListStatusReportsResponse\n additionalProperties: false\n description: ListStatusReportsResponse is the response containing status report summaries.\n openstatus.status_report.v1.PageComponentImpact:\n type: string\n title: PageComponentImpact\n enum:\n - PAGE_COMPONENT_IMPACT_UNSPECIFIED\n - PAGE_COMPONENT_IMPACT_OPERATIONAL\n - PAGE_COMPONENT_IMPACT_DEGRADED_PERFORMANCE\n - PAGE_COMPONENT_IMPACT_PARTIAL_OUTAGE\n - PAGE_COMPONENT_IMPACT_MAJOR_OUTAGE\n description: |-\n PageComponentImpact is the per-component impact a status report update sets.\n UNSPECIFIED means the caller doesn't speak impact (legacy) — it is NOT operational.\n openstatus.status_report.v1.StatusReport:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status report.\n status:\n title: status\n description: Current status of the report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n title:\n type: string\n title: title\n description: Title of the status report.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n updates:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportUpdate'\n title: updates\n description: Timeline of updates for this report (only included in GetStatusReport).\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the report was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the report was last updated (RFC 3339 format).\n title: StatusReport\n additionalProperties: false\n description: StatusReport represents an incident or maintenance report with full details.\n openstatus.status_report.v1.StatusReportStatus:\n type: string\n title: StatusReportStatus\n enum:\n - STATUS_REPORT_STATUS_UNSPECIFIED\n - STATUS_REPORT_STATUS_INVESTIGATING\n - STATUS_REPORT_STATUS_IDENTIFIED\n - STATUS_REPORT_STATUS_MONITORING\n - STATUS_REPORT_STATUS_RESOLVED\n description: StatusReportStatus represents the current state of a status report.\n openstatus.status_report.v1.StatusReportSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status report.\n status:\n title: status\n description: Current status of the report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n title:\n type: string\n title: title\n description: Title of the status report.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the report was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the report was last updated (RFC 3339 format).\n title: StatusReportSummary\n additionalProperties: false\n description: StatusReportSummary represents metadata for a status report (used in list responses).\n openstatus.status_report.v1.StatusReportUpdate:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the update.\n status:\n title: status\n description: Status at the time of this update.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n date:\n type: string\n title: date\n description: Timestamp when this update occurred (RFC 3339 format).\n message:\n type: string\n title: message\n description: Message describing the update.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the update was created (RFC 3339 format).\n componentImpacts:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.ComponentImpact'\n title: component_impacts\n description: Per-component impacts this update set (empty for legacy reports).\n title: StatusReportUpdate\n additionalProperties: false\n description: StatusReportUpdate represents a single update entry in a status report timeline.\n openstatus.status_report.v1.UpdateStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status report to update (required).\n title:\n type:\n - string\n - \"null\"\n title: title\n description: New title for the report (optional).\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: New list of page component IDs (optional, replaces existing list).\n updatePageComponentIds:\n type:\n - boolean\n - \"null\"\n title: update_page_component_ids\n description: |-\n Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved.\n title: UpdateStatusReportRequest\n additionalProperties: false\n description: UpdateStatusReportRequest is the request to update a status report's metadata.\n openstatus.status_report.v1.UpdateStatusReportResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The updated status report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: UpdateStatusReportResponse\n additionalProperties: false\n description: UpdateStatusReportResponse is the response after updating a status report.\nsecurity:\n - ApiKeyAuth: []\ntags:\n - name: MonitorService\n description: |\n Create, update, delete, and query monitors. Supports HTTP, TCP, and DNS monitor types\n with configurable check intervals, regions, assertions, and alerting thresholds.\n - name: StatusPageService\n description: |\n Manage public status pages with components, component groups, and email subscribers.\n Includes endpoints for retrieving full page content and aggregated status.\n - name: NotificationService\n description: |\n Configure notification channels (Slack, Discord, PagerDuty, email, webhooks, etc.)\n and associate them with monitors. Supports 12 notification providers.\n - name: StatusReportService\n description: |\n Create and manage incident reports with status updates. Reports follow a lifecycle:\n investigating -> identified -> monitoring -> resolved.\n - name: MaintenanceService\n description: |\n Schedule maintenance windows for status page components. Subscribers can be\n notified automatically when maintenance is created.\n - name: HealthService\n description: Health check endpoint for load balancer probes. No authentication required.\n - name: PrivateLocationService\n description: |-\n PrivateLocationService provides CRUD operations for private locations —\n self-hosted checker agents that run monitors from your own network.\npaths:\n /rpc/openstatus.health.v1.HealthService/Check:\n get:\n tags:\n - HealthService\n summary: Check\n description: Check returns the current serving status of the service.\n operationId: HealthService_Check.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckResponse'\n post:\n tags:\n - HealthService\n summary: Check\n description: Check returns the current serving status of the service.\n operationId: HealthService_Check\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/CreateMaintenance:\n post:\n tags:\n - MaintenanceService\n summary: CreateMaintenance\n description: CreateMaintenance creates a new maintenance window.\n operationId: MaintenanceService_CreateMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.CreateMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.CreateMaintenanceResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/DeleteMaintenance:\n post:\n tags:\n - MaintenanceService\n summary: DeleteMaintenance\n description: DeleteMaintenance removes a maintenance window.\n operationId: MaintenanceService_DeleteMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.DeleteMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.DeleteMaintenanceResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/GetMaintenance:\n get:\n tags:\n - MaintenanceService\n summary: GetMaintenance\n description: GetMaintenance retrieves a specific maintenance window by ID.\n operationId: MaintenanceService_GetMaintenance.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceResponse'\n post:\n tags:\n - MaintenanceService\n summary: GetMaintenance\n description: GetMaintenance retrieves a specific maintenance window by ID.\n operationId: MaintenanceService_GetMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/ListMaintenances:\n get:\n tags:\n - MaintenanceService\n summary: ListMaintenances\n description: ListMaintenances returns all maintenance windows for the workspace.\n operationId: MaintenanceService_ListMaintenances.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesResponse'\n post:\n tags:\n - MaintenanceService\n summary: ListMaintenances\n description: ListMaintenances returns all maintenance windows for the workspace.\n operationId: MaintenanceService_ListMaintenances\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/UpdateMaintenance:\n post:\n tags:\n - MaintenanceService\n summary: UpdateMaintenance\n description: UpdateMaintenance updates a maintenance window.\n operationId: MaintenanceService_UpdateMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.UpdateMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.UpdateMaintenanceResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateDNSMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateDNSMonitor\n description: CreateDNSMonitor creates a new DNS monitor.\n operationId: MonitorService_CreateDNSMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateDNSMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateDNSMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateGRPCMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateGRPCMonitor\n description: CreateGRPCMonitor creates a new gRPC health check monitor.\n operationId: MonitorService_CreateGRPCMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateGRPCMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateGRPCMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateHTTPMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateHTTPMonitor\n description: Creates a new HTTP monitor in the authenticated workspace. Configure the target URL, HTTP method, request headers and body, response assertions (status code, body content, headers), check periodicity, geographic regions, and optional OpenTelemetry export. The monitor starts checking immediately if set to active.\n operationId: MonitorService_CreateHTTPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateHTTPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateHTTPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateICMPMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateICMPMonitor\n description: CreateICMPMonitor creates a new ICMP monitor.\n operationId: MonitorService_CreateICMPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateICMPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateICMPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateTCPMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateTCPMonitor\n description: CreateTCPMonitor creates a new TCP monitor.\n operationId: MonitorService_CreateTCPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateTCPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateTCPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/DeleteMonitor:\n post:\n tags:\n - MonitorService\n summary: DeleteMonitor\n description: DeleteMonitor removes a monitor.\n operationId: MonitorService_DeleteMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.DeleteMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.DeleteMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitor:\n get:\n tags:\n - MonitorService\n summary: GetMonitor\n description: |-\n GetMonitor returns a single monitor by ID within the authenticated workspace.\n Returns the monitor configuration (HTTP, TCP, DNS, ICMP, or gRPC) using the MonitorConfig oneof type.\n operationId: MonitorService_GetMonitor.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitor\n description: |-\n GetMonitor returns a single monitor by ID within the authenticated workspace.\n Returns the monitor configuration (HTTP, TCP, DNS, ICMP, or gRPC) using the MonitorConfig oneof type.\n operationId: MonitorService_GetMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitorHTTPResponseLog:\n get:\n tags:\n - MonitorService\n summary: GetMonitorHTTPResponseLog\n description: GetMonitorHTTPResponseLog returns one response log for an HTTP monitor.\n operationId: MonitorService_GetMonitorHTTPResponseLog.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitorHTTPResponseLog\n description: GetMonitorHTTPResponseLog returns one response log for an HTTP monitor.\n operationId: MonitorService_GetMonitorHTTPResponseLog\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitorStatus:\n get:\n tags:\n - MonitorService\n summary: GetMonitorStatus\n description: GetMonitorStatus returns the current status of all regions for a monitor.\n operationId: MonitorService_GetMonitorStatus.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitorStatus\n description: GetMonitorStatus returns the current status of all regions for a monitor.\n operationId: MonitorService_GetMonitorStatus\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitorSummary:\n get:\n tags:\n - MonitorService\n summary: GetMonitorSummary\n description: Returns aggregated metrics for a monitor including latency percentiles (p50, p75, p90, p95, p99), request counts by status (successful, degraded, failed), and the timestamp of the last check. Metrics can be scoped to a time range (1 day, 7 days, or 14 days) and filtered by specific regions.\n operationId: MonitorService_GetMonitorSummary.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitorSummary\n description: Returns aggregated metrics for a monitor including latency percentiles (p50, p75, p90, p95, p99), request counts by status (successful, degraded, failed), and the timestamp of the last check. Metrics can be scoped to a time range (1 day, 7 days, or 14 days) and filtered by specific regions.\n operationId: MonitorService_GetMonitorSummary\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryResponse'\n /rpc/openstatus.monitor.v1.MonitorService/ListMonitorHTTPResponseLogs:\n get:\n tags:\n - MonitorService\n summary: ListMonitorHTTPResponseLogs\n description: ListMonitorHTTPResponseLogs returns paginated response logs for an HTTP monitor from the 14-day HTTP response-log window.\n operationId: MonitorService_ListMonitorHTTPResponseLogs.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse'\n post:\n tags:\n - MonitorService\n summary: ListMonitorHTTPResponseLogs\n description: ListMonitorHTTPResponseLogs returns paginated response logs for an HTTP monitor from the 14-day HTTP response-log window.\n operationId: MonitorService_ListMonitorHTTPResponseLogs\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse'\n /rpc/openstatus.monitor.v1.MonitorService/ListMonitors:\n get:\n tags:\n - MonitorService\n summary: ListMonitors\n description: ListMonitors returns a paginated list of all monitors in the workspace.\n operationId: MonitorService_ListMonitors.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsResponse'\n post:\n tags:\n - MonitorService\n summary: ListMonitors\n description: ListMonitors returns a paginated list of all monitors in the workspace.\n operationId: MonitorService_ListMonitors\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsResponse'\n /rpc/openstatus.monitor.v1.MonitorService/TriggerMonitor:\n post:\n tags:\n - MonitorService\n summary: TriggerMonitor\n description: Manually triggers an immediate check for the specified monitor across all configured regions. This operation is rate-limited under the synthetic-checks quota. A monitor run record is created and the check is dispatched to the checker service.\n operationId: MonitorService_TriggerMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.TriggerMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.TriggerMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateDNSMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateDNSMonitor\n description: UpdateDNSMonitor updates an existing DNS monitor.\n operationId: MonitorService_UpdateDNSMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateDNSMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateDNSMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateGRPCMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateGRPCMonitor\n description: UpdateGRPCMonitor updates an existing gRPC monitor.\n operationId: MonitorService_UpdateGRPCMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateGRPCMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateGRPCMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateHTTPMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateHTTPMonitor\n description: UpdateHTTPMonitor updates an existing HTTP monitor.\n operationId: MonitorService_UpdateHTTPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateHTTPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateHTTPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateICMPMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateICMPMonitor\n description: UpdateICMPMonitor updates an existing ICMP monitor.\n operationId: MonitorService_UpdateICMPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateICMPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateICMPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateTCPMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateTCPMonitor\n description: UpdateTCPMonitor updates an existing TCP monitor.\n operationId: MonitorService_UpdateTCPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateTCPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateTCPMonitorResponse'\n /rpc/openstatus.notification.v1.NotificationService/CheckNotificationLimit:\n get:\n tags:\n - NotificationService\n summary: CheckNotificationLimit\n description: CheckNotificationLimit checks if the workspace has reached its notification limit.\n operationId: NotificationService_CheckNotificationLimit.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitResponse'\n post:\n tags:\n - NotificationService\n summary: CheckNotificationLimit\n description: CheckNotificationLimit checks if the workspace has reached its notification limit.\n operationId: NotificationService_CheckNotificationLimit\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitResponse'\n /rpc/openstatus.notification.v1.NotificationService/CreateNotification:\n post:\n tags:\n - NotificationService\n summary: CreateNotification\n description: CreateNotification creates a new notification channel.\n operationId: NotificationService_CreateNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CreateNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CreateNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/DeleteNotification:\n post:\n tags:\n - NotificationService\n summary: DeleteNotification\n description: DeleteNotification removes a notification channel.\n operationId: NotificationService_DeleteNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.DeleteNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.DeleteNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/GetNotification:\n get:\n tags:\n - NotificationService\n summary: GetNotification\n description: GetNotification retrieves a notification channel by ID.\n operationId: NotificationService_GetNotification.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationResponse'\n post:\n tags:\n - NotificationService\n summary: GetNotification\n description: GetNotification retrieves a notification channel by ID.\n operationId: NotificationService_GetNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/ListNotifications:\n get:\n tags:\n - NotificationService\n summary: ListNotifications\n description: ListNotifications returns a list of notification channels.\n operationId: NotificationService_ListNotifications.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsResponse'\n post:\n tags:\n - NotificationService\n summary: ListNotifications\n description: ListNotifications returns a list of notification channels.\n operationId: NotificationService_ListNotifications\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsResponse'\n /rpc/openstatus.notification.v1.NotificationService/SendTestNotification:\n post:\n tags:\n - NotificationService\n summary: SendTestNotification\n description: Sends a test notification to the specified provider to verify that the configuration is correct. This does not require an existing notification channel - just provide the provider type and its configuration data. Returns success status and an error message if the test failed.\n operationId: NotificationService_SendTestNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.SendTestNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.SendTestNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/UpdateNotification:\n post:\n tags:\n - NotificationService\n summary: UpdateNotification\n description: UpdateNotification updates an existing notification channel.\n operationId: NotificationService_UpdateNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.UpdateNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.UpdateNotificationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/CreatePrivateLocation:\n post:\n tags:\n - PrivateLocationService\n summary: CreatePrivateLocation\n description: Creates a private location. The agent token is generated by the server and returned in the response - it cannot be supplied by the caller. Use the token to configure the agent so it can pull its monitors and report results.\n operationId: PrivateLocationService_CreatePrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.CreatePrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.CreatePrivateLocationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/DeletePrivateLocation:\n post:\n tags:\n - PrivateLocationService\n summary: DeletePrivateLocation\n description: DeletePrivateLocation removes a private location.\n operationId: PrivateLocationService_DeletePrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.DeletePrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.DeletePrivateLocationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/GetPrivateLocation:\n get:\n tags:\n - PrivateLocationService\n summary: GetPrivateLocation\n description: |-\n GetPrivateLocation retrieves a single private location by ID, including\n its agent token.\n operationId: PrivateLocationService_GetPrivateLocation.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationResponse'\n post:\n tags:\n - PrivateLocationService\n summary: GetPrivateLocation\n description: |-\n GetPrivateLocation retrieves a single private location by ID, including\n its agent token.\n operationId: PrivateLocationService_GetPrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/ListPrivateLocations:\n get:\n tags:\n - PrivateLocationService\n summary: ListPrivateLocations\n description: |-\n ListPrivateLocations returns a paginated list of private location\n summaries. Agent tokens are not included - use GetPrivateLocation.\n operationId: PrivateLocationService_ListPrivateLocations.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsResponse'\n post:\n tags:\n - PrivateLocationService\n summary: ListPrivateLocations\n description: |-\n ListPrivateLocations returns a paginated list of private location\n summaries. Agent tokens are not included - use GetPrivateLocation.\n operationId: PrivateLocationService_ListPrivateLocations\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/UpdatePrivateLocation:\n post:\n tags:\n - PrivateLocationService\n summary: UpdatePrivateLocation\n description: UpdatePrivateLocation updates a private location.\n operationId: PrivateLocationService_UpdatePrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.UpdatePrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.UpdatePrivateLocationResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/AddMonitorComponent:\n post:\n tags:\n - StatusPageService\n summary: AddMonitorComponent\n description: AddMonitorComponent adds a monitor-based component to a status page.\n operationId: StatusPageService_AddMonitorComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddMonitorComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddMonitorComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/AddStaticComponent:\n post:\n tags:\n - StatusPageService\n summary: AddStaticComponent\n description: AddStaticComponent adds a static component to a status page.\n operationId: StatusPageService_AddStaticComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddStaticComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddStaticComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/CreateComponentGroup:\n post:\n tags:\n - StatusPageService\n summary: CreateComponentGroup\n description: CreateComponentGroup creates a new component group.\n operationId: StatusPageService_CreateComponentGroup\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateComponentGroupRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateComponentGroupResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/CreatePageSubscription:\n post:\n tags:\n - StatusPageService\n summary: 'CreatePageSubscription: operator-added subscriber (email or webhook), no verification.'\n description: Operator-added subscriber (email or webhook) with no verification flow — the partner starts receiving notifications immediately. Supports Slack and Discord webhook URLs in addition to email. A management token is still generated so the subscriber can self-manage (update scope, unsubscribe) without operator involvement. Use this for vendor/partner integrations where consent is established out-of-band; use SubscribeToPage for subscriber-initiated signups that require double opt-in.\n operationId: StatusPageService_CreatePageSubscription\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreatePageSubscriptionRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreatePageSubscriptionResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/CreateStatusPage:\n post:\n tags:\n - StatusPageService\n summary: CreateStatusPage\n description: CreateStatusPage creates a new status page.\n operationId: StatusPageService_CreateStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateStatusPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/DeleteComponentGroup:\n post:\n tags:\n - StatusPageService\n summary: DeleteComponentGroup\n description: DeleteComponentGroup removes a component group.\n operationId: StatusPageService_DeleteComponentGroup\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteComponentGroupRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteComponentGroupResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/DeleteStatusPage:\n post:\n tags:\n - StatusPageService\n summary: DeleteStatusPage\n description: DeleteStatusPage removes a status page.\n operationId: StatusPageService_DeleteStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteStatusPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetOverallStatus:\n get:\n tags:\n - StatusPageService\n summary: GetOverallStatus\n description: 'Returns the overall status of a status page along with individual component statuses. The overall status is computed from active status reports and maintenances with the following priority: degraded (from active status reports) > maintenance (from active maintenance windows) > operational.'\n operationId: StatusPageService_GetOverallStatus.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusResponse'\n post:\n tags:\n - StatusPageService\n summary: GetOverallStatus\n description: 'Returns the overall status of a status page along with individual component statuses. The overall status is computed from active status reports and maintenances with the following priority: degraded (from active status reports) > maintenance (from active maintenance windows) > operational.'\n operationId: StatusPageService_GetOverallStatus\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponent:\n get:\n tags:\n - StatusPageService\n summary: GetPageComponent\n description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components.\n operationId: StatusPageService_GetPageComponent.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse'\n post:\n tags:\n - StatusPageService\n summary: GetPageComponent\n description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components.\n operationId: StatusPageService_GetPageComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponentDailySummary:\n get:\n tags:\n - StatusPageService\n summary: GetPageComponentDailySummary\n description: 'Returns per-component daily status buckets (ok/degraded/error/count plus a resolved status) merged with the incident, maintenance, and status-report timeline over the last N days (max 45). Suitable as a single source of truth for status-bar and uptime-calendar rendering. Supports two access paths: by id (authenticated, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetPageComponentDailySummary.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryResponse'\n post:\n tags:\n - StatusPageService\n summary: GetPageComponentDailySummary\n description: 'Returns per-component daily status buckets (ok/degraded/error/count plus a resolved status) merged with the incident, maintenance, and status-report timeline over the last N days (max 45). Suitable as a single source of truth for status-bar and uptime-calendar rendering. Supports two access paths: by id (authenticated, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetPageComponentDailySummary\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetStatusPage:\n get:\n tags:\n - StatusPageService\n summary: GetStatusPage\n description: GetStatusPage retrieves a specific status page by ID.\n operationId: StatusPageService_GetStatusPage.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageResponse'\n post:\n tags:\n - StatusPageService\n summary: GetStatusPage\n description: GetStatusPage retrieves a specific status page by ID.\n operationId: StatusPageService_GetStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetStatusPageContent:\n get:\n tags:\n - StatusPageService\n summary: GetStatusPageContent\n description: 'Returns the full content of a status page including its components, component groups, active status reports, and scheduled maintenances. Supports two access paths: by ID (requires authentication, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetStatusPageContent.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentResponse'\n post:\n tags:\n - StatusPageService\n summary: GetStatusPageContent\n description: 'Returns the full content of a status page including its components, component groups, active status reports, and scheduled maintenances. Supports two access paths: by ID (requires authentication, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetStatusPageContent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetStatusPageOverview:\n get:\n tags:\n - StatusPageService\n summary: GetStatusPageOverview\n description: 'Returns everything about a single status page in one authenticated call: the page, its rich rendering configuration, components, component groups, active/recent status reports, maintenances, and the computed overall + per-component statuses. Workspace-scoped by id; there is no public slug access path. Does not include uptime time-series — use GetPageComponentDailySummary for that.'\n operationId: StatusPageService_GetStatusPageOverview.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewResponse'\n post:\n tags:\n - StatusPageService\n summary: GetStatusPageOverview\n description: 'Returns everything about a single status page in one authenticated call: the page, its rich rendering configuration, components, component groups, active/recent status reports, maintenances, and the computed overall + per-component statuses. Workspace-scoped by id; there is no public slug access path. Does not include uptime time-series — use GetPageComponentDailySummary for that.'\n operationId: StatusPageService_GetStatusPageOverview\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/ListStatusPages:\n get:\n tags:\n - StatusPageService\n summary: ListStatusPages\n description: ListStatusPages returns all status pages for the workspace.\n operationId: StatusPageService_ListStatusPages.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesResponse'\n post:\n tags:\n - StatusPageService\n summary: ListStatusPages\n description: ListStatusPages returns all status pages for the workspace.\n operationId: StatusPageService_ListStatusPages\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/ListSubscribers:\n get:\n tags:\n - StatusPageService\n summary: ListSubscribers\n description: ListSubscribers returns all subscribers for a status page.\n operationId: StatusPageService_ListSubscribers.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersResponse'\n post:\n tags:\n - StatusPageService\n summary: ListSubscribers\n description: ListSubscribers returns all subscribers for a status page.\n operationId: StatusPageService_ListSubscribers\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/RemoveComponent:\n post:\n tags:\n - StatusPageService\n summary: RemoveComponent\n description: RemoveComponent removes a component from a status page.\n operationId: StatusPageService_RemoveComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.RemoveComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.RemoveComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/SubscribeToPage:\n post:\n tags:\n - StatusPageService\n summary: 'SubscribeToPage: end-user email self-signup with double opt-in verification.'\n description: End-user email self-signup with double opt-in verification. A verification email is sent and the subscription activates only after the recipient confirms. If the email was previously unsubscribed, the existing row is reactivated instead of a duplicate being created. Use this for subscriber-initiated signups on the public status page; use CreatePageSubscription for operator-initiated (vendor) subscriptions that should skip verification.\n operationId: StatusPageService_SubscribeToPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.SubscribeToPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.SubscribeToPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UnsubscribeFromPage:\n post:\n tags:\n - StatusPageService\n summary: UnsubscribeFromPage\n description: UnsubscribeFromPage removes a subscription from a status page.\n operationId: StatusPageService_UnsubscribeFromPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UnsubscribeFromPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UnsubscribeFromPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UpdateComponent:\n post:\n tags:\n - StatusPageService\n summary: UpdateComponent\n description: UpdateComponent updates an existing component.\n operationId: StatusPageService_UpdateComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UpdateComponentGroup:\n post:\n tags:\n - StatusPageService\n summary: UpdateComponentGroup\n description: UpdateComponentGroup updates an existing component group.\n operationId: StatusPageService_UpdateComponentGroup\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentGroupRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentGroupResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UpdateStatusPage:\n post:\n tags:\n - StatusPageService\n summary: UpdateStatusPage\n description: UpdateStatusPage updates an existing status page.\n operationId: StatusPageService_UpdateStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateStatusPageResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/AddStatusReportUpdate:\n post:\n tags:\n - StatusReportService\n summary: AddStatusReportUpdate\n description: 'Adds a new update entry to an existing status report and transitions the report to the specified status. Status reports follow a lifecycle: investigating -> identified -> monitoring -> resolved. If notify is true, subscribers of the associated page are notified by email about the update.'\n operationId: StatusReportService_AddStatusReportUpdate\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.AddStatusReportUpdateRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.AddStatusReportUpdateResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/CreateStatusReport:\n post:\n tags:\n - StatusReportService\n summary: CreateStatusReport\n description: Creates a new status report with an initial update entry. The report is associated with a status page and optionally specific page components. An initial StatusReportUpdate is created automatically with the provided status, message, and date. If notify is true, subscribers of the associated page are notified by email.\n operationId: StatusReportService_CreateStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.CreateStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.CreateStatusReportResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/DeleteStatusReport:\n post:\n tags:\n - StatusReportService\n summary: DeleteStatusReport\n description: DeleteStatusReport removes a status report and all its updates.\n operationId: StatusReportService_DeleteStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.DeleteStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.DeleteStatusReportResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/GetStatusReport:\n get:\n tags:\n - StatusReportService\n summary: GetStatusReport\n description: GetStatusReport retrieves a specific status report by ID (includes full update timeline).\n operationId: StatusReportService_GetStatusReport.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportResponse'\n post:\n tags:\n - StatusReportService\n summary: GetStatusReport\n description: GetStatusReport retrieves a specific status report by ID (includes full update timeline).\n operationId: StatusReportService_GetStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/ListStatusReports:\n get:\n tags:\n - StatusReportService\n summary: ListStatusReports\n description: ListStatusReports returns all status reports for the workspace (metadata only).\n operationId: StatusReportService_ListStatusReports.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsResponse'\n post:\n tags:\n - StatusReportService\n summary: ListStatusReports\n description: ListStatusReports returns all status reports for the workspace (metadata only).\n operationId: StatusReportService_ListStatusReports\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/UpdateStatusReport:\n post:\n tags:\n - StatusReportService\n summary: UpdateStatusReport\n description: UpdateStatusReport updates the metadata of a status report (title, page components).\n operationId: StatusReportService_UpdateStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.UpdateStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.UpdateStatusReportResponse'\n"; +export default "openapi: 3.1.0\ninfo:\n description: OpenStatus is a open-source status page platform with global uptime monitoring. The OpenStatus API allows you to interact with the OpenStatus platform programmatically. To get started you need to create an account on https://www.openstatus.dev/ and create an api token in your settings. Requests are rate limited per API key or token (600 per minute, 100 per 10 seconds); exceeding a limit returns HTTP 429 with a Retry-After header. See https://www.openstatus.dev/docs/reference/api-rate-limits.\n title: OpenStatus API\n version: v2.0.0\n contact:\n email: ping@openstatus.dev\n url: https://www.openstatus.dev\nservers:\n - url: https://api.openstatus.dev\n description: Production\nexternalDocs:\n description: OpenStatus Documentation\n url: https://www.openstatus.dev/docs\ncomponents:\n responses:\n RateLimited:\n description: Rate limit exceeded (Connect code resource_exhausted). Retry after the number of seconds in the Retry-After header. See https://www.openstatus.dev/docs/reference/api-rate-limits.\n headers:\n Retry-After:\n description: Seconds to wait before retrying.\n schema:\n type: integer\n example: 7\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n securitySchemes:\n ApiKeyAuth:\n type: apiKey\n in: header\n name: x-openstatus-key\n schemas:\n connect.error:\n type: object\n properties:\n code:\n type: string\n examples:\n - not_found\n enum:\n - canceled\n - unknown\n - invalid_argument\n - deadline_exceeded\n - not_found\n - already_exists\n - permission_denied\n - resource_exhausted\n - failed_precondition\n - aborted\n - out_of_range\n - unimplemented\n - internal\n - unavailable\n - data_loss\n - unauthenticated\n description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code].\n message:\n type: string\n description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client.\n details:\n type: array\n items:\n $ref: '#/components/schemas/connect.error_details.Any'\n description: A list of messages that carry the error details. There is no limit on the number of messages.\n title: Connect Error\n additionalProperties: true\n description: 'Error type returned by Connect: https://connectrpc.com/docs/go/errors/#http-representation'\n connect.error_details.Any:\n type: object\n properties:\n type:\n type: string\n description: 'A URL that acts as a globally unique identifier for the type of the serialized message. For example: `type.googleapis.com/google.rpc.ErrorInfo`. This is used to determine the schema of the data in the `value` field and is the discriminator for the `debug` field.'\n value:\n type: string\n format: binary\n description: The Protobuf message, serialized as bytes and base64-encoded. The specific message type is identified by the `type` field.\n debug:\n oneOf:\n - type: object\n title: Any\n additionalProperties: true\n description: Detailed error information.\n discriminator:\n propertyName: type\n title: Debug\n description: Deserialized error detail payload. The 'type' field indicates the schema. This field is for easier debugging and should not be relied upon for application logic.\n additionalProperties: true\n description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message, with an additional debug field for ConnectRPC error details.\n openstatus.health.v1.CheckRequest:\n type: object\n properties:\n service:\n type: string\n title: service\n description: Optional service name to check. If empty, checks overall service health.\n title: CheckRequest\n additionalProperties: false\n description: CheckRequest is the request message for health checks.\n openstatus.health.v1.CheckResponse:\n type: object\n properties:\n status:\n title: status\n description: The serving status of the service.\n $ref: '#/components/schemas/openstatus.health.v1.CheckResponse.ServingStatus'\n title: CheckResponse\n additionalProperties: false\n description: CheckResponse is the response message for health checks.\n openstatus.health.v1.CheckResponse.ServingStatus:\n type: string\n title: ServingStatus\n enum:\n - SERVING_STATUS_UNSPECIFIED\n - SERVING_STATUS_SERVING\n - SERVING_STATUS_NOT_SERVING\n description: ServingStatus represents the health status of the service.\n openstatus.incident.v1.AddIncidentNoteRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident (required).\n message:\n type: string\n title: message\n maxLength: 10000\n minLength: 1\n description: Note text, markdown (required, 1-10000 characters).\n title: AddIncidentNoteRequest\n additionalProperties: false\n description: AddIncidentNoteRequest is the request to add a note to an incident timeline.\n openstatus.incident.v1.AddIncidentNoteResponse:\n type: object\n properties:\n event:\n title: event\n description: The timeline event that was added.\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentEvent'\n title: AddIncidentNoteResponse\n additionalProperties: false\n description: AddIncidentNoteResponse is the response after adding a note.\n openstatus.incident.v1.ApprovePostmortemRequest:\n type: object\n properties:\n incidentId:\n type: string\n title: incident_id\n minLength: 1\n description: ID of the incident (required).\n close:\n type:\n - boolean\n - \"null\"\n title: close\n description: Also close the incident (optional, defaults to false).\n title: ApprovePostmortemRequest\n additionalProperties: false\n description: ApprovePostmortemRequest is the request to approve the postmortem of an incident.\n openstatus.incident.v1.ApprovePostmortemResponse:\n type: object\n properties:\n postmortem:\n title: postmortem\n description: The approved postmortem.\n $ref: '#/components/schemas/openstatus.incident.v1.Postmortem'\n incident:\n title: incident\n description: The incident after approval (without its timeline).\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: ApprovePostmortemResponse\n additionalProperties: false\n description: ApprovePostmortemResponse is the response after approving the postmortem.\n openstatus.incident.v1.CloseIncidentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident (required).\n skipPostmortem:\n type:\n - boolean\n - \"null\"\n title: skip_postmortem\n description: Close without an approved postmortem (optional, defaults to false).\n title: CloseIncidentRequest\n additionalProperties: false\n description: CloseIncidentRequest is the request to close a resolved incident.\n openstatus.incident.v1.CloseIncidentResponse:\n type: object\n properties:\n incident:\n title: incident\n description: The closed incident (without its timeline).\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: CloseIncidentResponse\n additionalProperties: false\n description: CloseIncidentResponse is the response after closing an incident.\n openstatus.incident.v1.DeclareIncidentRequest:\n type: object\n properties:\n title:\n type: string\n examples:\n - Checkout API returns 502\n title: title\n maxLength: 256\n minLength: 1\n description: Title of the incident (required, 1-256 characters).\n severity:\n not:\n enum:\n - INCIDENT_SEVERITY_UNSPECIFIED\n title: severity\n description: Severity of the incident (required).\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity'\n summary:\n type:\n - string\n - \"null\"\n title: summary\n maxLength: 4000\n description: Short human summary (optional, up to 4000 characters).\n commanderEmail:\n type:\n - string\n - \"null\"\n examples:\n - jane@example.com\n title: commander_email\n format: email\n description: Email of the member who leads the response (optional, defaults to unassigned).\n startedAt:\n type:\n - string\n - \"null\"\n examples:\n - \"2024-03-15T10:30:00Z\"\n title: started_at\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: When the impact began (RFC 3339 format, optional, defaults to now).\n statusReportId:\n type:\n - string\n - \"null\"\n title: status_report_id\n minLength: 1\n description: ID of a status report to link (optional).\n openSlackChannel:\n type:\n - boolean\n - \"null\"\n title: open_slack_channel\n description: Whether to open a Slack channel for the incident when Slack is connected (optional, defaults to false).\n title: DeclareIncidentRequest\n additionalProperties: false\n description: DeclareIncidentRequest is the request to declare a new incident.\n openstatus.incident.v1.DeclareIncidentResponse:\n type: object\n properties:\n incident:\n title: incident\n description: The declared incident.\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: DeclareIncidentResponse\n additionalProperties: false\n description: DeclareIncidentResponse is the response after declaring an incident.\n openstatus.incident.v1.DeleteIncidentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident (required).\n title: DeleteIncidentRequest\n additionalProperties: false\n description: DeleteIncidentRequest is the request to delete an incident.\n openstatus.incident.v1.DeleteIncidentResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteIncidentResponse\n additionalProperties: false\n description: DeleteIncidentResponse is the response after deleting an incident.\n openstatus.incident.v1.GetIncidentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident to retrieve (required).\n title: GetIncidentRequest\n additionalProperties: false\n description: GetIncidentRequest is the request to get an incident by ID.\n openstatus.incident.v1.GetIncidentResponse:\n type: object\n properties:\n incident:\n title: incident\n description: The requested incident.\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: GetIncidentResponse\n additionalProperties: false\n description: GetIncidentResponse is the response containing the incident and its timeline.\n openstatus.incident.v1.GetPostmortemRequest:\n type: object\n properties:\n incidentId:\n type: string\n title: incident_id\n minLength: 1\n description: ID of the incident (required).\n title: GetPostmortemRequest\n additionalProperties: false\n description: GetPostmortemRequest is the request to get the postmortem of an incident.\n openstatus.incident.v1.GetPostmortemResponse:\n type: object\n properties:\n postmortem:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.Postmortem'\n - type: \"null\"\n title: postmortem\n description: The postmortem (unset when none has been written yet).\n title: GetPostmortemResponse\n additionalProperties: false\n description: GetPostmortemResponse is the response containing the postmortem, if any.\n openstatus.incident.v1.Incident:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the incident.\n title:\n type: string\n title: title\n description: Title of the incident.\n severity:\n title: severity\n description: Severity of the incident.\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity'\n status:\n title: status\n description: Current status of the incident.\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus'\n summary:\n type:\n - string\n - \"null\"\n title: summary\n description: Short human summary.\n commander:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser'\n - type: \"null\"\n title: commander\n description: Member leading the response (unset when unassigned).\n declaredBy:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser'\n - type: \"null\"\n title: declared_by\n description: Member who declared the incident (unset for API keys without a creator).\n resolvedBy:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser'\n - type: \"null\"\n title: resolved_by\n description: Member who last resolved the incident.\n declaredAt:\n type: string\n title: declared_at\n description: Timestamp when the incident was declared (RFC 3339 format).\n startedAt:\n type: string\n title: started_at\n description: Timestamp when the impact began (RFC 3339 format).\n mitigatedAt:\n type:\n - string\n - \"null\"\n title: mitigated_at\n description: Timestamp when the incident was first mitigated (RFC 3339 format).\n resolvedAt:\n type:\n - string\n - \"null\"\n title: resolved_at\n description: Timestamp of the last resolution (RFC 3339 format).\n closedAt:\n type:\n - string\n - \"null\"\n title: closed_at\n description: |-\n Timestamp when the incident was closed or canceled (RFC 3339 format).\n A closed incident is read-only except for its postmortem.\n statusReport:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatusReport'\n - type: \"null\"\n title: status_report\n description: Linked public status report.\n slackChannelUrl:\n type:\n - string\n - \"null\"\n title: slack_channel_url\n description: Link to the bound Slack channel.\n allowedTransitions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus'\n title: allowed_transitions\n description: Statuses SetIncidentStatus accepts from the current state (empty once closed).\n deletable:\n type: boolean\n title: deletable\n description: 'Whether DeleteIncident is allowed: only while open and never mitigated, resolved or closed.'\n events:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentEvent'\n title: events\n description: Timeline, newest first (only included in GetIncident).\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the incident was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the incident was last updated (RFC 3339 format).\n title: Incident\n additionalProperties: false\n description: Incident is a managed incident with full details.\n openstatus.incident.v1.IncidentEvent:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the event.\n type:\n title: type\n description: Kind of event.\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentEventType'\n message:\n type: string\n title: message\n description: 'Text of the event: the note for notes, a rendered summary otherwise.'\n createdBy:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser'\n - type: \"null\"\n title: created_by\n description: Member who caused the event (unset for system events and API keys without a creator).\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the event was recorded (RFC 3339 format).\n title: IncidentEvent\n additionalProperties: false\n description: IncidentEvent is one entry of an incident timeline.\n openstatus.incident.v1.IncidentEventType:\n type: string\n title: IncidentEventType\n enum:\n - INCIDENT_EVENT_TYPE_UNSPECIFIED\n - INCIDENT_EVENT_TYPE_DECLARED\n - INCIDENT_EVENT_TYPE_SEVERITY_CHANGED\n - INCIDENT_EVENT_TYPE_STATUS_CHANGED\n - INCIDENT_EVENT_TYPE_COMMANDER_CHANGED\n - INCIDENT_EVENT_TYPE_STARTED_AT_CHANGED\n - INCIDENT_EVENT_TYPE_NOTE\n - INCIDENT_EVENT_TYPE_STATUS_REPORT_LINKED\n - INCIDENT_EVENT_TYPE_STATUS_REPORT_UNLINKED\n - INCIDENT_EVENT_TYPE_SLACK_CHANNEL_BOUND\n - INCIDENT_EVENT_TYPE_SLACK_CHANNEL_UNBOUND\n - INCIDENT_EVENT_TYPE_RESOLVED\n - INCIDENT_EVENT_TYPE_CANCELED\n - INCIDENT_EVENT_TYPE_POSTMORTEM_DRAFTED\n - INCIDENT_EVENT_TYPE_POSTMORTEM_UPDATED\n - INCIDENT_EVENT_TYPE_POSTMORTEM_APPROVED\n - INCIDENT_EVENT_TYPE_CLOSED\n description: IncidentEventType is the kind of entry in an incident timeline.\n openstatus.incident.v1.IncidentSeverity:\n type: string\n title: IncidentSeverity\n enum:\n - INCIDENT_SEVERITY_UNSPECIFIED\n - INCIDENT_SEVERITY_CRITICAL\n - INCIDENT_SEVERITY_MAJOR\n - INCIDENT_SEVERITY_MINOR\n description: IncidentSeverity is how bad an incident is.\n openstatus.incident.v1.IncidentStatus:\n type: string\n title: IncidentStatus\n enum:\n - INCIDENT_STATUS_UNSPECIFIED\n - INCIDENT_STATUS_OPEN\n - INCIDENT_STATUS_MITIGATED\n - INCIDENT_STATUS_RESOLVED\n - INCIDENT_STATUS_CANCELED\n description: |-\n IncidentStatus is the lifecycle state of an incident.\n Closed is not a status: a closed incident has closed_at set and keeps its last status.\n openstatus.incident.v1.IncidentStatusReport:\n type: object\n properties:\n id:\n type: string\n title: id\n description: ID of the status report.\n title:\n type: string\n title: title\n description: Title of the status report.\n status:\n title: status\n description: Current status of the status report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n pageId:\n type: string\n title: page_id\n description: ID of the status page the report belongs to.\n title: IncidentStatusReport\n additionalProperties: false\n description: IncidentStatusReport is the public status report linked to an incident.\n openstatus.incident.v1.IncidentSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the incident.\n title:\n type: string\n title: title\n description: Title of the incident.\n severity:\n title: severity\n description: Severity of the incident.\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity'\n status:\n title: status\n description: Current status of the incident.\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus'\n commander:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser'\n - type: \"null\"\n title: commander\n description: Member leading the response (unset when unassigned).\n declaredAt:\n type: string\n title: declared_at\n description: Timestamp when the incident was declared (RFC 3339 format).\n startedAt:\n type: string\n title: started_at\n description: Timestamp when the impact began (RFC 3339 format).\n resolvedAt:\n type:\n - string\n - \"null\"\n title: resolved_at\n description: Timestamp of the last resolution (RFC 3339 format).\n closedAt:\n type:\n - string\n - \"null\"\n title: closed_at\n description: Timestamp when the incident was closed or canceled (RFC 3339 format).\n statusReport:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatusReport'\n - type: \"null\"\n title: status_report\n description: Linked public status report.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the incident was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the incident was last updated (RFC 3339 format).\n title: IncidentSummary\n additionalProperties: false\n description: IncidentSummary is the metadata of an incident (used in list responses).\n openstatus.incident.v1.IncidentUser:\n type: object\n properties:\n email:\n type: string\n title: email\n description: Email address of the member (empty for a deleted account).\n name:\n type: string\n title: name\n description: Display name of the member (\"Deleted user\" for a deleted account).\n title: IncidentUser\n additionalProperties: false\n description: IncidentUser is a workspace member referenced by an incident.\n openstatus.incident.v1.LinkStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident (required).\n statusReportId:\n type: string\n title: status_report_id\n minLength: 1\n description: ID of the status report to link (required).\n title: LinkStatusReportRequest\n additionalProperties: false\n description: LinkStatusReportRequest is the request to link a status report to an incident.\n openstatus.incident.v1.LinkStatusReportResponse:\n type: object\n properties:\n incident:\n title: incident\n description: The updated incident (without its timeline).\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: LinkStatusReportResponse\n additionalProperties: false\n description: LinkStatusReportResponse is the response after linking a status report.\n openstatus.incident.v1.ListIncidentsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of incidents to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of incidents to skip for pagination (defaults to 0).\n statuses:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus'\n title: statuses\n description: Filter by status (optional). If empty, returns all statuses.\n closed:\n type:\n - boolean\n - \"null\"\n title: closed\n description: Filter by closed state (optional). If unset, returns both.\n title: ListIncidentsRequest\n additionalProperties: false\n description: ListIncidentsRequest is the request to list incidents.\n openstatus.incident.v1.ListIncidentsResponse:\n type: object\n properties:\n incidents:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentSummary'\n title: incidents\n description: List of incidents (metadata only, use GetIncident for full details).\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of incidents matching the filter.\n title: ListIncidentsResponse\n additionalProperties: false\n description: ListIncidentsResponse is the response containing incident summaries.\n openstatus.incident.v1.Postmortem:\n type: object\n properties:\n incidentId:\n type: string\n title: incident_id\n description: ID of the incident the postmortem belongs to.\n status:\n title: status\n description: Review state of the postmortem.\n $ref: '#/components/schemas/openstatus.incident.v1.PostmortemStatus'\n content:\n type: string\n title: content\n description: Markdown body of the postmortem.\n draftedBy:\n title: drafted_by\n description: Who wrote the current body.\n $ref: '#/components/schemas/openstatus.incident.v1.PostmortemAuthor'\n approvedBy:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser'\n - type: \"null\"\n title: approved_by\n description: Member who approved the postmortem.\n approvedAt:\n type:\n - string\n - \"null\"\n title: approved_at\n description: Timestamp when the postmortem was approved (RFC 3339 format).\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the postmortem was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the postmortem was last updated (RFC 3339 format).\n title: Postmortem\n additionalProperties: false\n description: Postmortem is the review written after an incident is resolved.\n openstatus.incident.v1.PostmortemAuthor:\n type: string\n title: PostmortemAuthor\n enum:\n - POSTMORTEM_AUTHOR_UNSPECIFIED\n - POSTMORTEM_AUTHOR_AGENT\n - POSTMORTEM_AUTHOR_USER\n description: PostmortemAuthor is who wrote the current postmortem body.\n openstatus.incident.v1.PostmortemStatus:\n type: string\n title: PostmortemStatus\n enum:\n - POSTMORTEM_STATUS_UNSPECIFIED\n - POSTMORTEM_STATUS_DRAFT\n - POSTMORTEM_STATUS_APPROVED\n description: PostmortemStatus is the review state of a postmortem.\n openstatus.incident.v1.SetIncidentStatusRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident (required).\n status:\n not:\n enum:\n - INCIDENT_STATUS_UNSPECIFIED\n title: status\n description: Target status (required).\n $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus'\n note:\n type:\n - string\n - \"null\"\n title: note\n maxLength: 10000\n description: Note recorded with the change (optional, up to 10000 characters).\n title: SetIncidentStatusRequest\n additionalProperties: false\n description: SetIncidentStatusRequest is the request to change the status of an incident.\n openstatus.incident.v1.SetIncidentStatusResponse:\n type: object\n properties:\n incident:\n title: incident\n description: The updated incident (without its timeline).\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: SetIncidentStatusResponse\n additionalProperties: false\n description: SetIncidentStatusResponse is the response after changing the status of an incident.\n openstatus.incident.v1.UnlinkStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident (required).\n title: UnlinkStatusReportRequest\n additionalProperties: false\n description: UnlinkStatusReportRequest is the request to unlink the status report of an incident.\n openstatus.incident.v1.UnlinkStatusReportResponse:\n type: object\n properties:\n incident:\n title: incident\n description: The updated incident (without its timeline).\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: UnlinkStatusReportResponse\n additionalProperties: false\n description: UnlinkStatusReportResponse is the response after unlinking the status report.\n openstatus.incident.v1.UpdateIncidentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the incident to update (required).\n title:\n type:\n - string\n - \"null\"\n title: title\n maxLength: 256\n minLength: 1\n description: New title (optional, 1-256 characters).\n severity:\n oneOf:\n - $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity'\n - type: \"null\"\n not:\n enum:\n - INCIDENT_SEVERITY_UNSPECIFIED\n title: severity\n description: New severity (optional).\n summary:\n type:\n - string\n - \"null\"\n title: summary\n maxLength: 4000\n minLength: 1\n description: New summary (optional, 1-4000 characters).\n clearSummary:\n type:\n - boolean\n - \"null\"\n title: clear_summary\n description: Set to true to remove the summary. Cannot be combined with summary.\n commanderEmail:\n type:\n - string\n - \"null\"\n title: commander_email\n format: email\n description: Email of the new commander (optional).\n clearCommander:\n type:\n - boolean\n - \"null\"\n title: clear_commander\n description: Set to true to unassign the commander. Cannot be combined with commander_email.\n startedAt:\n type:\n - string\n - \"null\"\n title: started_at\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: New start of impact (RFC 3339 format, optional).\n title: UpdateIncidentRequest\n additionalProperties: false\n description: UpdateIncidentRequest is the request to edit an incident.\n openstatus.incident.v1.UpdateIncidentResponse:\n type: object\n properties:\n incident:\n title: incident\n description: The updated incident (without its timeline).\n $ref: '#/components/schemas/openstatus.incident.v1.Incident'\n title: UpdateIncidentResponse\n additionalProperties: false\n description: UpdateIncidentResponse is the response after updating an incident.\n openstatus.incident.v1.UpdatePostmortemRequest:\n type: object\n properties:\n incidentId:\n type: string\n title: incident_id\n minLength: 1\n description: ID of the incident (required).\n content:\n type: string\n title: content\n maxLength: 100000\n minLength: 1\n description: Markdown body (required, 1-100000 characters). Replaces the current body; an approved postmortem stays approved.\n title: UpdatePostmortemRequest\n additionalProperties: false\n description: UpdatePostmortemRequest is the request to write the postmortem of a resolved incident.\n openstatus.incident.v1.UpdatePostmortemResponse:\n type: object\n properties:\n postmortem:\n title: postmortem\n description: The saved postmortem.\n $ref: '#/components/schemas/openstatus.incident.v1.Postmortem'\n title: UpdatePostmortemResponse\n additionalProperties: false\n description: UpdatePostmortemResponse is the response after writing the postmortem.\n openstatus.maintenance.v1.CreateMaintenanceRequest:\n type: object\n properties:\n title:\n type: string\n examples:\n - Database Migration\n title: title\n maxLength: 256\n minLength: 1\n description: Title of the maintenance (required, 1-256 characters).\n message:\n type: string\n title: message\n minLength: 1\n description: Message describing the maintenance (required).\n from:\n type: string\n examples:\n - \"2024-03-01T02:00:00Z\"\n title: from\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: Start time of the maintenance window (RFC 3339 format, required).\n to:\n type: string\n examples:\n - \"2024-03-01T06:00:00Z\"\n title: to\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: End time of the maintenance window (RFC 3339 format, required).\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: Page ID to associate with this maintenance (required).\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: Page component IDs to associate with this maintenance (optional).\n notify:\n type:\n - boolean\n - \"null\"\n title: notify\n description: Whether to notify subscribers about this maintenance (optional, defaults to false).\n title: CreateMaintenanceRequest\n additionalProperties: false\n description: CreateMaintenanceRequest is the request to create a new maintenance window.\n openstatus.maintenance.v1.CreateMaintenanceResponse:\n type: object\n properties:\n maintenance:\n title: maintenance\n description: The created maintenance.\n $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance'\n title: CreateMaintenanceResponse\n additionalProperties: false\n description: CreateMaintenanceResponse is the response after creating a maintenance window.\n openstatus.maintenance.v1.DeleteMaintenanceRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the maintenance to delete (required).\n title: DeleteMaintenanceRequest\n additionalProperties: false\n description: DeleteMaintenanceRequest is the request to delete a maintenance window.\n openstatus.maintenance.v1.DeleteMaintenanceResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteMaintenanceResponse\n additionalProperties: false\n description: DeleteMaintenanceResponse is the response after deleting a maintenance window.\n openstatus.maintenance.v1.GetMaintenanceRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the maintenance to retrieve (required).\n title: GetMaintenanceRequest\n additionalProperties: false\n description: GetMaintenanceRequest is the request to get a maintenance window by ID.\n openstatus.maintenance.v1.GetMaintenanceResponse:\n type: object\n properties:\n maintenance:\n title: maintenance\n description: The requested maintenance.\n $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance'\n title: GetMaintenanceResponse\n additionalProperties: false\n description: GetMaintenanceResponse is the response containing the maintenance window.\n openstatus.maintenance.v1.ListMaintenancesRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of maintenances to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of maintenances to skip for pagination (defaults to 0).\n pageId:\n type:\n - string\n - \"null\"\n title: page_id\n description: Filter by page ID (optional).\n title: ListMaintenancesRequest\n additionalProperties: false\n description: ListMaintenancesRequest is the request to list maintenance windows.\n openstatus.maintenance.v1.ListMaintenancesResponse:\n type: object\n properties:\n maintenances:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary'\n title: maintenances\n description: List of maintenances.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of maintenances matching the filter.\n title: ListMaintenancesResponse\n additionalProperties: false\n description: ListMaintenancesResponse is the response containing maintenance window summaries.\n openstatus.maintenance.v1.Maintenance:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the maintenance.\n title:\n type: string\n title: title\n description: Title of the maintenance.\n message:\n type: string\n title: message\n description: Message describing the maintenance.\n from:\n type: string\n title: from\n description: Start time of the maintenance window (RFC 3339 format).\n to:\n type: string\n title: to\n description: End time of the maintenance window (RFC 3339 format).\n pageId:\n type: string\n title: page_id\n description: ID of the page this maintenance is associated with.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the maintenance was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the maintenance was last updated (RFC 3339 format).\n title: Maintenance\n additionalProperties: false\n description: Maintenance represents a maintenance window with full details.\n openstatus.maintenance.v1.MaintenanceSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the maintenance.\n title:\n type: string\n title: title\n description: Title of the maintenance.\n message:\n type: string\n title: message\n description: Message describing the maintenance.\n from:\n type: string\n title: from\n description: Start time of the maintenance window (RFC 3339 format).\n to:\n type: string\n title: to\n description: End time of the maintenance window (RFC 3339 format).\n pageId:\n type: string\n title: page_id\n description: ID of the page this maintenance is associated with.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the maintenance was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the maintenance was last updated (RFC 3339 format).\n title: MaintenanceSummary\n additionalProperties: false\n description: MaintenanceSummary represents metadata for a maintenance window (used in list responses).\n openstatus.maintenance.v1.UpdateMaintenanceRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the maintenance to update (required).\n title:\n type:\n - string\n - \"null\"\n title: title\n maxLength: 256\n minLength: 1\n description: New title for the maintenance (optional).\n message:\n type:\n - string\n - \"null\"\n title: message\n description: New message for the maintenance (optional).\n from:\n type:\n - string\n - \"null\"\n title: from\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: New start time (RFC 3339 format, optional).\n to:\n type:\n - string\n - \"null\"\n title: to\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: New end time (RFC 3339 format, optional).\n pageId:\n type:\n - string\n - \"null\"\n title: page_id\n description: 'Deprecated: page_id is now derived from page_component_ids.'\n deprecated: true\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: New list of page component IDs (optional, replaces existing list).\n updatePageComponentIds:\n type:\n - boolean\n - \"null\"\n title: update_page_component_ids\n description: |-\n Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved.\n title: UpdateMaintenanceRequest\n additionalProperties: false\n description: UpdateMaintenanceRequest is the request to update a maintenance window.\n openstatus.maintenance.v1.UpdateMaintenanceResponse:\n type: object\n properties:\n maintenance:\n title: maintenance\n description: The updated maintenance.\n $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance'\n title: UpdateMaintenanceResponse\n additionalProperties: false\n description: UpdateMaintenanceResponse is the response after updating a maintenance window.\n openstatus.monitor.v1.BodyAssertion:\n type: object\n properties:\n target:\n type: string\n title: target\n description: Target value to compare against.\n comparator:\n not:\n enum:\n - STRING_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator'\n title: BodyAssertion\n additionalProperties: false\n description: BodyAssertion defines an assertion for response body content.\n openstatus.monitor.v1.CreateDNSMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: CreateDNSMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateDNSMonitorRequest is the request to create a new DNS monitor.\n openstatus.monitor.v1.CreateDNSMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: CreateDNSMonitorResponse\n additionalProperties: false\n description: CreateDNSMonitorResponse is the response after creating a DNS monitor.\n openstatus.monitor.v1.CreateGRPCMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: CreateGRPCMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateGRPCMonitorRequest is the request to create a new gRPC monitor.\n openstatus.monitor.v1.CreateGRPCMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: CreateGRPCMonitorResponse\n additionalProperties: false\n description: CreateGRPCMonitorResponse is the response after creating a gRPC monitor.\n openstatus.monitor.v1.CreateHTTPMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: CreateHTTPMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateHTTPMonitorRequest is the request to create a new HTTP monitor.\n openstatus.monitor.v1.CreateHTTPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: CreateHTTPMonitorResponse\n additionalProperties: false\n description: CreateHTTPMonitorResponse is the response after creating an HTTP monitor.\n openstatus.monitor.v1.CreateICMPMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: CreateICMPMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateICMPMonitorRequest is the request to create a new ICMP monitor.\n openstatus.monitor.v1.CreateICMPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: CreateICMPMonitorResponse\n additionalProperties: false\n description: CreateICMPMonitorResponse is the response after creating an ICMP monitor.\n openstatus.monitor.v1.CreateTCPMonitorRequest:\n type: object\n properties:\n monitor:\n title: monitor\n description: Monitor configuration (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: CreateTCPMonitorRequest\n required:\n - monitor\n additionalProperties: false\n description: CreateTCPMonitorRequest is the request to create a new TCP monitor.\n openstatus.monitor.v1.CreateTCPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The created monitor with assigned ID.\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: CreateTCPMonitorResponse\n additionalProperties: false\n description: CreateTCPMonitorResponse is the response after creating a TCP monitor.\n openstatus.monitor.v1.DNSMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - DNS Resolution Check\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - example.com\n title: uri\n maxLength: 2048\n minLength: 1\n description: Domain to resolve (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n recordAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.RecordAssertion'\n title: record_assertions\n maxItems: 10\n description: DNS record assertions for validation.\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: DNSMonitor\n additionalProperties: false\n description: DNSMonitor defines the configuration for a DNS monitor.\n openstatus.monitor.v1.DeleteMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to delete (required).\n title: DeleteMonitorRequest\n additionalProperties: false\n description: DeleteMonitorRequest is the request to delete a monitor.\n openstatus.monitor.v1.DeleteMonitorResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteMonitorResponse\n additionalProperties: false\n description: DeleteMonitorResponse is the response after deleting a monitor.\n openstatus.monitor.v1.GRPCMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Checkout gRPC\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - api.example.com:443\n title: uri\n maxLength: 2048\n minLength: 1\n pattern: ^(\\[[0-9a-fA-F:]+\\]|[^:/\\s]+):[0-9]{1,5}$\n description: Target in \"host:port\" form. IPv6 addresses must be bracketed.\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n service:\n type:\n - string\n - \"null\"\n examples:\n - checkout.v1.CheckoutService\n title: service\n maxLength: 512\n description: Service name passed to Health/Check. Empty means overall server health.\n tlsMode:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.GRPCTlsMode'\n - type: \"null\"\n title: tls_mode\n description: How the connection to the target is secured. Defaults to TLS.\n metadata:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Headers'\n title: metadata\n maxItems: 20\n description: Metadata sent with the health check request.\n title: GRPCMonitor\n additionalProperties: false\n description: |-\n GRPCMonitor defines the configuration for a gRPC health check monitor.\n The probe calls grpc.health.v1.Health/Check on the target.\n openstatus.monitor.v1.GRPCTlsMode:\n type: string\n title: GRPCTlsMode\n enum:\n - GRPC_TLS_MODE_UNSPECIFIED\n - GRPC_TLS_MODE_TLS\n - GRPC_TLS_MODE_PLAINTEXT\n - GRPC_TLS_MODE_TLS_INSECURE\n description: GRPCTlsMode selects how the probe secures its connection to the target.\n openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to get a response log for (required).\n logId:\n type: string\n title: log_id\n minLength: 1\n description: Response log ID to retrieve (required).\n title: GetMonitorHTTPResponseLogRequest\n additionalProperties: false\n description: GetMonitorHTTPResponseLogRequest is the request to get one response log.\n openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse:\n type: object\n properties:\n log:\n title: log\n description: Response log details.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogDetail'\n title: GetMonitorHTTPResponseLogResponse\n additionalProperties: false\n description: GetMonitorHTTPResponseLogResponse is the response containing one response log.\n openstatus.monitor.v1.GetMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to retrieve (required).\n title: GetMonitorRequest\n additionalProperties: false\n description: GetMonitorRequest is the request to get a single monitor by ID.\n openstatus.monitor.v1.GetMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The monitor configuration (one of HTTP, TCP, DNS, ICMP, or gRPC).\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorConfig'\n title: GetMonitorResponse\n additionalProperties: false\n description: GetMonitorResponse is the response containing the monitor.\n openstatus.monitor.v1.GetMonitorStatusRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to get status for (required).\n title: GetMonitorStatusRequest\n additionalProperties: false\n description: GetMonitorStatusRequest is the request to get the status of all regions for a monitor.\n openstatus.monitor.v1.GetMonitorStatusResponse:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Monitor ID.\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.RegionStatus'\n title: regions\n description: Status for each region.\n title: GetMonitorStatusResponse\n additionalProperties: false\n description: GetMonitorStatusResponse is the response containing the status of all regions for a monitor.\n openstatus.monitor.v1.GetMonitorSummaryRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to get summary for (required).\n timeRange:\n title: time_range\n description: Time range for metrics aggregation (defaults to 1 day if unspecified).\n $ref: '#/components/schemas/openstatus.monitor.v1.TimeRange'\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Optional filter by regions. If empty, returns metrics for all regions.\n title: GetMonitorSummaryRequest\n additionalProperties: false\n description: GetMonitorSummaryRequest is the request to get aggregated metrics for a monitor.\n openstatus.monitor.v1.GetMonitorSummaryResponse:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Monitor ID.\n lastPingAt:\n type: string\n title: last_ping_at\n description: Timestamp of the last check in RFC 3339 format.\n totalSuccessful:\n type:\n - integer\n - string\n title: total_successful\n format: int64\n description: Total number of successful requests.\n totalDegraded:\n type:\n - integer\n - string\n title: total_degraded\n format: int64\n description: Total number of degraded requests.\n totalFailed:\n type:\n - integer\n - string\n title: total_failed\n format: int64\n description: Total number of failed requests.\n p50:\n type:\n - integer\n - string\n title: p50\n format: int64\n description: 50th percentile (median) latency in milliseconds.\n p75:\n type:\n - integer\n - string\n title: p75\n format: int64\n description: 75th percentile latency in milliseconds.\n p90:\n type:\n - integer\n - string\n title: p90\n format: int64\n description: 90th percentile latency in milliseconds.\n p95:\n type:\n - integer\n - string\n title: p95\n format: int64\n description: 95th percentile latency in milliseconds.\n p99:\n type:\n - integer\n - string\n title: p99\n format: int64\n description: 99th percentile latency in milliseconds.\n timeRange:\n title: time_range\n description: Time range used for the metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.TimeRange'\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n description: Regions included in the metrics.\n title: GetMonitorSummaryResponse\n additionalProperties: false\n description: GetMonitorSummaryResponse is the response containing aggregated metrics for a monitor.\n openstatus.monitor.v1.HTTPMethod:\n type: string\n title: HTTPMethod\n enum:\n - HTTP_METHOD_UNSPECIFIED\n - HTTP_METHOD_GET\n - HTTP_METHOD_POST\n - HTTP_METHOD_HEAD\n - HTTP_METHOD_PUT\n - HTTP_METHOD_PATCH\n - HTTP_METHOD_DELETE\n - HTTP_METHOD_TRACE\n - HTTP_METHOD_CONNECT\n - HTTP_METHOD_OPTIONS\n description: HTTP methods supported for monitors.\n openstatus.monitor.v1.HTTPMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Production API Health Check\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n url:\n type: string\n examples:\n - https://api.example.com/health\n title: url\n maxLength: 2048\n minLength: 1\n format: uri\n description: URL to monitor (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n method:\n not:\n enum:\n - HTTP_METHOD_UNSPECIFIED\n title: method\n description: HTTP method to use (defaults to GET).\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMethod'\n body:\n type: string\n examples:\n - map[key:value]\n title: body\n description: Request body (optional).\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n followRedirects:\n type:\n - boolean\n - \"null\"\n title: follow_redirects\n description: Whether to follow HTTP redirects (defaults to true when not specified).\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Headers'\n title: headers\n maxItems: 20\n description: Custom headers for the request.\n statusCodeAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.StatusCodeAssertion'\n title: status_code_assertions\n maxItems: 10\n description: Status code assertions for the response.\n bodyAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.BodyAssertion'\n title: body_assertions\n maxItems: 10\n description: Body content assertions for the response.\n headerAssertions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.HeaderAssertion'\n title: header_assertions\n maxItems: 10\n description: Header assertions for the response.\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: HTTPMonitor\n additionalProperties: false\n description: HTTPMonitor defines the configuration for an HTTP monitor.\n openstatus.monitor.v1.HTTPResponseLogDetail:\n type: object\n properties:\n log:\n title: log\n description: Compact response log fields.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem'\n url:\n type: string\n title: url\n description: Checked URL.\n error:\n type: boolean\n title: error\n description: Whether the check errored.\n message:\n type:\n - string\n - \"null\"\n title: message\n description: Error message, when present.\n headers:\n type: object\n title: headers\n additionalProperties:\n type: string\n title: value\n description: Redacted response headers.\n assertions:\n type:\n - string\n - \"null\"\n title: assertions\n description: Serialized assertions used for the check.\n title: HTTPResponseLogDetail\n additionalProperties: false\n description: HTTPResponseLogDetail contains full response log debugging data.\n openstatus.monitor.v1.HTTPResponseLogDetail.HeadersEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: HeadersEntry\n additionalProperties: false\n openstatus.monitor.v1.HTTPResponseLogListItem:\n type: object\n properties:\n id:\n type:\n - string\n - \"null\"\n title: id\n description: Response log ID.\n latency:\n type: integer\n title: latency\n format: int32\n description: Latency in milliseconds.\n statusCode:\n type:\n - integer\n - \"null\"\n title: status_code\n format: int32\n description: HTTP status code.\n monitorId:\n type: string\n title: monitor_id\n description: Monitor ID.\n requestStatus:\n title: request_status\n description: Request status classification.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogRequestStatus'\n region:\n title: region\n description: Region where the check ran.\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n cronTimestamp:\n type:\n - integer\n - string\n title: cron_timestamp\n format: int64\n description: Cron bucket timestamp in Unix milliseconds.\n trigger:\n title: trigger\n description: Check trigger.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTrigger'\n timestamp:\n type:\n - integer\n - string\n title: timestamp\n format: int64\n description: Response timestamp in Unix milliseconds.\n timing:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTiming'\n - type: \"null\"\n title: timing\n description: Timing phases.\n title: HTTPResponseLogListItem\n additionalProperties: false\n description: HTTPResponseLogListItem is a compact response log entry.\n openstatus.monitor.v1.HTTPResponseLogPagination:\n type: object\n properties:\n limit:\n type: integer\n title: limit\n format: int32\n description: Requested page size.\n offset:\n type: integer\n title: offset\n format: int32\n description: Requested offset.\n hasMore:\n type: boolean\n title: has_more\n description: Whether more logs are available.\n nextOffset:\n type:\n - integer\n - \"null\"\n title: next_offset\n format: int32\n description: Next offset if more logs are available.\n title: HTTPResponseLogPagination\n additionalProperties: false\n description: HTTPResponseLogPagination contains offset pagination metadata.\n openstatus.monitor.v1.HTTPResponseLogRequestStatus:\n type: string\n title: HTTPResponseLogRequestStatus\n enum:\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_UNSPECIFIED\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_SUCCESS\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_ERROR\n - HTTP_RESPONSE_LOG_REQUEST_STATUS_DEGRADED\n description: HTTPResponseLogRequestStatus is the result classification for an HTTP response log.\n openstatus.monitor.v1.HTTPResponseLogTiming:\n type: object\n properties:\n dns:\n type: integer\n title: dns\n format: int32\n description: DNS lookup duration.\n connect:\n type: integer\n title: connect\n format: int32\n description: TCP connection duration.\n tls:\n type: integer\n title: tls\n format: int32\n description: TLS handshake duration.\n ttfb:\n type: integer\n title: ttfb\n format: int32\n description: Time to first byte duration.\n transfer:\n type: integer\n title: transfer\n format: int32\n description: Response transfer duration.\n title: HTTPResponseLogTiming\n additionalProperties: false\n description: HTTPResponseLogTiming contains calculated timing phases in milliseconds.\n openstatus.monitor.v1.HTTPResponseLogTrigger:\n type: string\n title: HTTPResponseLogTrigger\n enum:\n - HTTP_RESPONSE_LOG_TRIGGER_UNSPECIFIED\n - HTTP_RESPONSE_LOG_TRIGGER_CRON\n - HTTP_RESPONSE_LOG_TRIGGER_API\n description: HTTPResponseLogTrigger describes what started the monitor check.\n openstatus.monitor.v1.HeaderAssertion:\n type: object\n properties:\n target:\n type: string\n title: target\n description: Target value to compare against.\n comparator:\n not:\n enum:\n - STRING_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator'\n key:\n type: string\n title: key\n minLength: 1\n description: Header key to check (required).\n title: HeaderAssertion\n additionalProperties: false\n description: HeaderAssertion defines an assertion for response headers.\n openstatus.monitor.v1.Headers:\n type: object\n properties:\n key:\n type: string\n examples:\n - Authorization\n title: key\n minLength: 1\n description: Header name.\n value:\n type: string\n examples:\n - Bearer token123\n title: value\n description: Header value.\n title: Headers\n additionalProperties: false\n description: Headers represents a key-value pair for HTTP headers.\n openstatus.monitor.v1.ICMPMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Ping Gateway\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - 1.1.1.1\n title: uri\n maxLength: 2048\n minLength: 1\n description: URI to monitor in format \"host or IP\" (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: ICMPMonitor\n additionalProperties: false\n description: ICMPMonitor defines the configuration for a ICMP monitor.\n openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to list response logs for (required).\n fromTimestamp:\n type:\n - integer\n - string\n - \"null\"\n title: from_timestamp\n format: int64\n description: Start of the response log window as Unix milliseconds within the 14-day retention window.\n toTimestamp:\n type:\n - integer\n - string\n - \"null\"\n title: to_timestamp\n format: int64\n description: End of the response log window as Unix milliseconds within the 14-day retention window.\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of logs to return (1-100, defaults to 25).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of logs to skip for pagination (defaults to 0).\n title: ListMonitorHTTPResponseLogsRequest\n additionalProperties: false\n description: ListMonitorHTTPResponseLogsRequest is the request to list response logs within the 14-day HTTP response-log window.\n openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse:\n type: object\n properties:\n logs:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem'\n title: logs\n description: Response logs.\n pagination:\n title: pagination\n description: Pagination metadata.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPResponseLogPagination'\n title: ListMonitorHTTPResponseLogsResponse\n additionalProperties: false\n description: ListMonitorHTTPResponseLogsResponse is the response containing response logs.\n openstatus.monitor.v1.ListMonitorsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of monitors to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of monitors to skip for pagination (defaults to 0).\n title: ListMonitorsRequest\n additionalProperties: false\n description: ListMonitorsRequest is the request to list monitors.\n openstatus.monitor.v1.ListMonitorsResponse:\n type: object\n properties:\n httpMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: http_monitors\n description: HTTP monitors in the workspace.\n tcpMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: tcp_monitors\n description: TCP monitors in the workspace.\n dnsMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: dns_monitors\n description: DNS monitors in the workspace.\n icmpMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: icmp_monitors\n description: ICMP monitors in the workspace.\n grpcMonitors:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: grpc_monitors\n description: gRPC monitors in the workspace.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of monitors across all types.\n title: ListMonitorsResponse\n additionalProperties: false\n description: ListMonitorsResponse is the response containing a list of monitors.\n openstatus.monitor.v1.MonitorConfig:\n type: object\n oneOf:\n - type: object\n properties:\n dns:\n title: dns\n description: DNS monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: dns\n required:\n - dns\n - type: object\n properties:\n grpc:\n title: grpc\n description: gRPC monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: grpc\n required:\n - grpc\n - type: object\n properties:\n http:\n title: http\n description: HTTP monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: http\n required:\n - http\n - type: object\n properties:\n icmp:\n title: icmp\n description: ICMP monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: icmp\n required:\n - icmp\n - type: object\n properties:\n tcp:\n title: tcp\n description: TCP monitor configuration.\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: tcp\n required:\n - tcp\n title: MonitorConfig\n additionalProperties: false\n description: MonitorConfig represents the type-specific configuration for a monitor.\n openstatus.monitor.v1.MonitorStatus:\n type: string\n title: MonitorStatus\n enum:\n - MONITOR_STATUS_UNSPECIFIED\n - MONITOR_STATUS_ACTIVE\n - MONITOR_STATUS_DEGRADED\n - MONITOR_STATUS_ERROR\n description: MonitorStatus represents the operational status of a monitor.\n openstatus.monitor.v1.NumberComparator:\n type: string\n title: NumberComparator\n enum:\n - NUMBER_COMPARATOR_UNSPECIFIED\n - NUMBER_COMPARATOR_EQUAL\n - NUMBER_COMPARATOR_NOT_EQUAL\n - NUMBER_COMPARATOR_GREATER_THAN\n - NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL\n - NUMBER_COMPARATOR_LESS_THAN\n - NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL\n description: NumberComparator defines comparison operations for numeric values.\n openstatus.monitor.v1.OpenTelemetryConfig:\n type: object\n properties:\n endpoint:\n type: string\n title: endpoint\n maxLength: 2048\n description: OTEL endpoint URL.\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Headers'\n title: headers\n maxItems: 20\n description: Custom headers for OTEL requests.\n title: OpenTelemetryConfig\n additionalProperties: false\n description: OpenTelemetry configuration for exporting metrics.\n openstatus.monitor.v1.Periodicity:\n type: string\n title: Periodicity\n enum:\n - PERIODICITY_UNSPECIFIED\n - PERIODICITY_30S\n - PERIODICITY_1M\n - PERIODICITY_5M\n - PERIODICITY_10M\n - PERIODICITY_30M\n - PERIODICITY_1H\n description: Monitor periodicity options.\n openstatus.monitor.v1.RecordAssertion:\n type: object\n properties:\n record:\n type: string\n title: record\n enum:\n - A\n - AAAA\n - CNAME\n - MX\n - TXT\n description: DNS record type (e.g., \"A\", \"AAAA\", \"CNAME\", \"MX\", \"TXT\").\n comparator:\n not:\n enum:\n - RECORD_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.RecordComparator'\n target:\n type: string\n title: target\n description: Target value to compare against.\n title: RecordAssertion\n additionalProperties: false\n description: RecordAssertion defines an assertion for DNS records.\n openstatus.monitor.v1.RecordComparator:\n type: string\n title: RecordComparator\n enum:\n - RECORD_COMPARATOR_UNSPECIFIED\n - RECORD_COMPARATOR_EQUAL\n - RECORD_COMPARATOR_NOT_EQUAL\n - RECORD_COMPARATOR_CONTAINS\n - RECORD_COMPARATOR_NOT_CONTAINS\n description: RecordComparator defines comparison operations for DNS records.\n openstatus.monitor.v1.Region:\n type: string\n title: Region\n enum:\n - REGION_UNSPECIFIED\n - REGION_FLY_AMS\n - REGION_FLY_ARN\n - REGION_FLY_BOM\n - REGION_FLY_CDG\n - REGION_FLY_DFW\n - REGION_FLY_EWR\n - REGION_FLY_FRA\n - REGION_FLY_GRU\n - REGION_FLY_IAD\n - REGION_FLY_JNB\n - REGION_FLY_LAX\n - REGION_FLY_LHR\n - REGION_FLY_NRT\n - REGION_FLY_ORD\n - REGION_FLY_SJC\n - REGION_FLY_SIN\n - REGION_FLY_SYD\n - REGION_FLY_YYZ\n - REGION_KOYEB_FRA\n - REGION_KOYEB_PAR\n - REGION_KOYEB_SFO\n - REGION_KOYEB_SIN\n - REGION_KOYEB_TYO\n - REGION_KOYEB_WAS\n - REGION_RAILWAY_US_WEST2\n - REGION_RAILWAY_US_EAST4\n - REGION_RAILWAY_EUROPE_WEST4\n - REGION_RAILWAY_ASIA_SOUTHEAST1\n description: |-\n Geographic regions where monitors can run checks from.\n REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN.\n openstatus.monitor.v1.RegionStatus:\n type: object\n properties:\n region:\n title: region\n description: The region identifier.\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n status:\n title: status\n description: The status of the monitor in this region.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n title: RegionStatus\n additionalProperties: false\n description: RegionStatus represents the status of a monitor in a specific region.\n openstatus.monitor.v1.StatusCodeAssertion:\n type: object\n properties:\n target:\n type:\n - integer\n - string\n title: target\n maximum: 599\n minimum: 100\n format: int64\n description: Target status code to compare against (100-599).\n comparator:\n not:\n enum:\n - NUMBER_COMPARATOR_UNSPECIFIED\n title: comparator\n description: Comparison operation (required, must not be UNSPECIFIED).\n $ref: '#/components/schemas/openstatus.monitor.v1.NumberComparator'\n title: StatusCodeAssertion\n additionalProperties: false\n description: StatusCodeAssertion defines an assertion for HTTP status codes.\n openstatus.monitor.v1.StringComparator:\n type: string\n title: StringComparator\n enum:\n - STRING_COMPARATOR_UNSPECIFIED\n - STRING_COMPARATOR_CONTAINS\n - STRING_COMPARATOR_NOT_CONTAINS\n - STRING_COMPARATOR_EQUAL\n - STRING_COMPARATOR_NOT_EQUAL\n - STRING_COMPARATOR_EMPTY\n - STRING_COMPARATOR_NOT_EMPTY\n - STRING_COMPARATOR_GREATER_THAN\n - STRING_COMPARATOR_GREATER_THAN_OR_EQUAL\n - STRING_COMPARATOR_LESS_THAN\n - STRING_COMPARATOR_LESS_THAN_OR_EQUAL\n description: StringComparator defines comparison operations for string values.\n openstatus.monitor.v1.TCPMonitor:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the monitor (output only for create requests).\n name:\n type: string\n examples:\n - Database Connection Check\n title: name\n maxLength: 256\n minLength: 1\n description: Name of the monitor (required, max 256 characters).\n uri:\n type: string\n examples:\n - tcp://db.example.com:5432\n title: uri\n maxLength: 2048\n minLength: 1\n description: URI to monitor in format \"host:port\" (required, max 2048 characters).\n periodicity:\n not:\n enum:\n - PERIODICITY_UNSPECIFIED\n title: periodicity\n description: Check periodicity (required).\n $ref: '#/components/schemas/openstatus.monitor.v1.Periodicity'\n timeout:\n type:\n - integer\n - string\n title: timeout\n maximum: 120000\n minimum: 0\n format: int64\n description: Timeout in milliseconds (0-120000, defaults to 45000).\n degradedAt:\n type:\n - integer\n - string\n - \"null\"\n title: degraded_at\n maximum: 120000\n minimum: 0\n format: int64\n description: Latency threshold for degraded status in milliseconds (optional, 0-120000).\n retry:\n type:\n - integer\n - string\n title: retry\n maximum: 10\n minimum: 0\n format: int64\n description: Number of retry attempts (0-10, defaults to 3).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the monitor (optional).\n active:\n type:\n - boolean\n - \"null\"\n title: active\n description: Whether the monitor is active (defaults to false).\n public:\n type:\n - boolean\n - \"null\"\n title: public\n description: Whether the monitor is publicly visible (defaults to false).\n regions:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.monitor.v1.Region'\n title: regions\n maxItems: 28\n description: Geographic regions to run checks from.\n openTelemetry:\n title: open_telemetry\n description: OpenTelemetry configuration for exporting metrics.\n $ref: '#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig'\n status:\n title: status\n description: Current operational status of the monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.MonitorStatus'\n privateLocationIds:\n type: array\n items:\n type: string\n readOnly: true\n title: private_location_ids\n description: IDs of private locations that run this monitor. Read-only.\n readOnly: true\n title: TCPMonitor\n additionalProperties: false\n description: TCPMonitor defines the configuration for a TCP monitor.\n openstatus.monitor.v1.TimeRange:\n type: string\n title: TimeRange\n enum:\n - TIME_RANGE_UNSPECIFIED\n - TIME_RANGE_1D\n - TIME_RANGE_7D\n - TIME_RANGE_14D\n description: TimeRange represents the time period for metrics aggregation.\n openstatus.monitor.v1.TriggerMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to trigger (required).\n title: TriggerMonitorRequest\n additionalProperties: false\n description: TriggerMonitorRequest is the request to trigger a monitor check.\n openstatus.monitor.v1.TriggerMonitorResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the trigger was successful.\n title: TriggerMonitorResponse\n additionalProperties: false\n description: TriggerMonitorResponse is the response after triggering a monitor.\n openstatus.monitor.v1.UpdateDNSMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateDNSMonitorRequest\n additionalProperties: false\n description: UpdateDNSMonitorRequest is the request to update an existing DNS monitor.\n openstatus.monitor.v1.UpdateDNSMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.DNSMonitor'\n title: UpdateDNSMonitorResponse\n additionalProperties: false\n description: UpdateDNSMonitorResponse is the response after updating a DNS monitor.\n openstatus.monitor.v1.UpdateGRPCMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateGRPCMonitorRequest\n additionalProperties: false\n description: UpdateGRPCMonitorRequest is the request to update an existing gRPC monitor.\n openstatus.monitor.v1.UpdateGRPCMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.GRPCMonitor'\n title: UpdateGRPCMonitorResponse\n additionalProperties: false\n description: UpdateGRPCMonitorResponse is the response after updating a gRPC monitor.\n openstatus.monitor.v1.UpdateHTTPMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateHTTPMonitorRequest\n additionalProperties: false\n description: UpdateHTTPMonitorRequest is the request to update an existing HTTP monitor.\n openstatus.monitor.v1.UpdateHTTPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.HTTPMonitor'\n title: UpdateHTTPMonitorResponse\n additionalProperties: false\n description: UpdateHTTPMonitorResponse is the response after updating an HTTP monitor.\n openstatus.monitor.v1.UpdateICMPMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateICMPMonitorRequest\n additionalProperties: false\n description: UpdateICMPMonitorRequest is the request to update an existing ICMP monitor.\n openstatus.monitor.v1.UpdateICMPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.ICMPMonitor'\n title: UpdateICMPMonitorResponse\n additionalProperties: false\n description: UpdateICMPMonitorResponse is the response after updating an ICMP monitor.\n openstatus.monitor.v1.UpdateTCPMonitorRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Monitor ID to update (required).\n monitor:\n oneOf:\n - $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n - type: \"null\"\n title: monitor\n description: Updated monitor configuration (all fields optional for partial updates).\n title: UpdateTCPMonitorRequest\n additionalProperties: false\n description: UpdateTCPMonitorRequest is the request to update an existing TCP monitor.\n openstatus.monitor.v1.UpdateTCPMonitorResponse:\n type: object\n properties:\n monitor:\n title: monitor\n description: The updated monitor.\n $ref: '#/components/schemas/openstatus.monitor.v1.TCPMonitor'\n title: UpdateTCPMonitorResponse\n additionalProperties: false\n description: UpdateTCPMonitorResponse is the response after updating a TCP monitor.\n openstatus.notification.v1.CheckNotificationLimitRequest:\n type: object\n title: CheckNotificationLimitRequest\n additionalProperties: false\n description: CheckNotificationLimitRequest is the request to check notification limits.\n openstatus.notification.v1.CheckNotificationLimitResponse:\n type: object\n properties:\n limitReached:\n type: boolean\n title: limit_reached\n description: Whether the workspace has reached its notification limit.\n currentCount:\n type: integer\n title: current_count\n format: int32\n description: Current number of notification channels.\n maxCount:\n type: integer\n title: max_count\n format: int32\n description: Maximum allowed notification channels.\n title: CheckNotificationLimitResponse\n additionalProperties: false\n description: CheckNotificationLimitResponse is the response containing limit information.\n openstatus.notification.v1.CreateNotificationRequest:\n type: object\n properties:\n name:\n type: string\n examples:\n - Slack Ops Channel\n title: name\n minLength: 1\n description: Display name for the notification channel.\n provider:\n not:\n enum:\n - NOTIFICATION_PROVIDER_UNSPECIFIED\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n data:\n title: data\n description: Provider-specific configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors to associate with this notification.\n title: CreateNotificationRequest\n required:\n - data\n additionalProperties: false\n description: CreateNotificationRequest is the request to create a new notification channel.\n openstatus.notification.v1.CreateNotificationResponse:\n type: object\n properties:\n notification:\n title: notification\n description: The created notification channel.\n $ref: '#/components/schemas/openstatus.notification.v1.Notification'\n title: CreateNotificationResponse\n additionalProperties: false\n description: CreateNotificationResponse is the response after creating a notification channel.\n openstatus.notification.v1.DeleteNotificationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Notification ID to delete (required).\n title: DeleteNotificationRequest\n additionalProperties: false\n description: DeleteNotificationRequest is the request to delete a notification channel.\n openstatus.notification.v1.DeleteNotificationResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteNotificationResponse\n additionalProperties: false\n description: DeleteNotificationResponse is the response after deleting a notification channel.\n openstatus.notification.v1.DiscordData:\n type: object\n properties:\n webhookUrl:\n type: string\n examples:\n - https://discord.com/api/webhooks/123/abc\n title: webhook_url\n format: uri\n description: Discord webhook URL.\n title: DiscordData\n additionalProperties: false\n description: DiscordData contains configuration for Discord notifications.\n openstatus.notification.v1.EmailData:\n type: object\n properties:\n email:\n type: string\n examples:\n - ops-team@example.com\n title: email\n format: email\n description: Email address to send notifications to.\n title: EmailData\n additionalProperties: false\n description: EmailData contains configuration for email notifications.\n openstatus.notification.v1.GetNotificationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Notification ID to retrieve (required).\n title: GetNotificationRequest\n additionalProperties: false\n description: GetNotificationRequest is the request to get a notification channel.\n openstatus.notification.v1.GetNotificationResponse:\n type: object\n properties:\n notification:\n title: notification\n description: The notification channel.\n $ref: '#/components/schemas/openstatus.notification.v1.Notification'\n title: GetNotificationResponse\n additionalProperties: false\n description: GetNotificationResponse is the response containing the notification channel.\n openstatus.notification.v1.GoogleChatData:\n type: object\n properties:\n webhookUrl:\n type: string\n title: webhook_url\n format: uri\n description: Google Chat webhook URL.\n title: GoogleChatData\n additionalProperties: false\n description: GoogleChatData contains configuration for Google Chat notifications.\n openstatus.notification.v1.GrafanaOncallData:\n type: object\n properties:\n webhookUrl:\n type: string\n title: webhook_url\n format: uri\n description: Grafana OnCall webhook URL.\n title: GrafanaOncallData\n additionalProperties: false\n description: GrafanaOncallData contains configuration for Grafana OnCall notifications.\n openstatus.notification.v1.ListNotificationsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of notifications to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of notifications to skip for pagination (defaults to 0).\n title: ListNotificationsRequest\n additionalProperties: false\n description: ListNotificationsRequest is the request to list notification channels.\n openstatus.notification.v1.ListNotificationsResponse:\n type: object\n properties:\n notifications:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationSummary'\n title: notifications\n description: Notification channel summaries.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of notification channels.\n title: ListNotificationsResponse\n additionalProperties: false\n description: ListNotificationsResponse is the response containing notification channels.\n openstatus.notification.v1.MsTeamsData:\n type: object\n properties:\n webhookUrl:\n type: string\n examples:\n - https://prod-00.westeurope.logic.azure.com:443/workflows/abc/triggers/manual/paths/invoke\n title: webhook_url\n format: uri\n description: Microsoft Teams webhook URL (Power Automate Workflows).\n title: MsTeamsData\n additionalProperties: false\n description: MsTeamsData contains configuration for Microsoft Teams notifications.\n openstatus.notification.v1.Notification:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the notification.\n name:\n type: string\n title: name\n description: Display name for the notification channel.\n provider:\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n data:\n title: data\n description: Provider-specific configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors associated with this notification.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the notification was created (RFC 3339).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the notification was last updated (RFC 3339).\n title: Notification\n additionalProperties: false\n description: Notification represents a notification channel with full details.\n openstatus.notification.v1.NotificationData:\n type: object\n oneOf:\n - type: object\n properties:\n discord:\n title: discord\n description: Discord configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.DiscordData'\n title: discord\n required:\n - discord\n - type: object\n properties:\n email:\n title: email\n description: Email configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.EmailData'\n title: email\n required:\n - email\n - type: object\n properties:\n googleChat:\n title: google_chat\n description: Google Chat configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.GoogleChatData'\n title: google_chat\n required:\n - googleChat\n - type: object\n properties:\n grafanaOncall:\n title: grafana_oncall\n description: Grafana OnCall configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.GrafanaOncallData'\n title: grafana_oncall\n required:\n - grafanaOncall\n - type: object\n properties:\n msTeams:\n title: ms_teams\n description: Microsoft Teams configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.MsTeamsData'\n title: ms_teams\n required:\n - msTeams\n - type: object\n properties:\n ntfy:\n title: ntfy\n description: Ntfy configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NtfyData'\n title: ntfy\n required:\n - ntfy\n - type: object\n properties:\n opsgenie:\n title: opsgenie\n description: Opsgenie configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.OpsgenieData'\n title: opsgenie\n required:\n - opsgenie\n - type: object\n properties:\n pagerduty:\n title: pagerduty\n description: PagerDuty configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.PagerDutyData'\n title: pagerduty\n required:\n - pagerduty\n - type: object\n properties:\n slack:\n title: slack\n description: Slack configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.SlackData'\n title: slack\n required:\n - slack\n - type: object\n properties:\n sms:\n title: sms\n description: 'Deprecated: SMS is no longer offered; use whatsapp.'\n deprecated: true\n $ref: '#/components/schemas/openstatus.notification.v1.SmsData'\n title: sms\n required:\n - sms\n - type: object\n properties:\n telegram:\n title: telegram\n description: Telegram configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.TelegramData'\n title: telegram\n required:\n - telegram\n - type: object\n properties:\n webhook:\n title: webhook\n description: Webhook configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.WebhookData'\n title: webhook\n required:\n - webhook\n - type: object\n properties:\n whatsapp:\n title: whatsapp\n description: WhatsApp configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.WhatsappData'\n title: whatsapp\n required:\n - whatsapp\n title: NotificationData\n additionalProperties: false\n description: NotificationData is a union of provider-specific configuration.\n openstatus.notification.v1.NotificationProvider:\n type: string\n title: NotificationProvider\n enum:\n - NOTIFICATION_PROVIDER_UNSPECIFIED\n - NOTIFICATION_PROVIDER_DISCORD\n - NOTIFICATION_PROVIDER_EMAIL\n - NOTIFICATION_PROVIDER_GOOGLE_CHAT\n - NOTIFICATION_PROVIDER_GRAFANA_ONCALL\n - NOTIFICATION_PROVIDER_NTFY\n - NOTIFICATION_PROVIDER_PAGERDUTY\n - NOTIFICATION_PROVIDER_OPSGENIE\n - NOTIFICATION_PROVIDER_SLACK\n - NOTIFICATION_PROVIDER_SMS\n - NOTIFICATION_PROVIDER_TELEGRAM\n - NOTIFICATION_PROVIDER_WEBHOOK\n - NOTIFICATION_PROVIDER_WHATSAPP\n - NOTIFICATION_PROVIDER_MS_TEAMS\n description: NotificationProvider represents the supported notification channel types.\n openstatus.notification.v1.NotificationSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the notification.\n name:\n type: string\n title: name\n description: Display name for the notification channel.\n provider:\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n monitorCount:\n type: integer\n title: monitor_count\n format: int32\n description: Number of monitors associated with this notification.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the notification was created (RFC 3339).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the notification was last updated (RFC 3339).\n title: NotificationSummary\n additionalProperties: false\n description: NotificationSummary represents a notification channel summary for list responses.\n openstatus.notification.v1.NtfyData:\n type: object\n properties:\n topic:\n type: string\n examples:\n - openstatus-alerts\n title: topic\n minLength: 1\n description: Ntfy topic to publish to.\n serverUrl:\n type: string\n title: server_url\n description: Ntfy server URL (defaults to https://ntfy.sh).\n token:\n type:\n - string\n - \"null\"\n title: token\n description: Optional authentication token.\n title: NtfyData\n additionalProperties: false\n description: NtfyData contains configuration for Ntfy notifications.\n openstatus.notification.v1.OpsgenieData:\n type: object\n properties:\n apiKey:\n type: string\n title: api_key\n minLength: 1\n description: Opsgenie API key.\n region:\n title: region\n description: Opsgenie region.\n $ref: '#/components/schemas/openstatus.notification.v1.OpsgenieRegion'\n title: OpsgenieData\n additionalProperties: false\n description: OpsgenieData contains configuration for Opsgenie notifications.\n openstatus.notification.v1.OpsgenieRegion:\n type: string\n title: OpsgenieRegion\n enum:\n - OPSGENIE_REGION_UNSPECIFIED\n - OPSGENIE_REGION_US\n - OPSGENIE_REGION_EU\n description: OpsgenieRegion represents the Opsgenie API region.\n openstatus.notification.v1.PagerDutyData:\n type: object\n properties:\n integrationKey:\n type: string\n examples:\n - a1b2c3d4e5f6g7h8i9j0\n title: integration_key\n minLength: 1\n description: PagerDuty integration key.\n title: PagerDutyData\n additionalProperties: false\n description: PagerDutyData contains configuration for PagerDuty notifications.\n openstatus.notification.v1.SendTestNotificationRequest:\n type: object\n properties:\n provider:\n not:\n enum:\n - NOTIFICATION_PROVIDER_UNSPECIFIED\n title: provider\n description: Provider type.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationProvider'\n data:\n title: data\n description: Provider-specific configuration.\n $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n title: SendTestNotificationRequest\n required:\n - data\n additionalProperties: false\n description: SendTestNotificationRequest is the request to send a test notification.\n openstatus.notification.v1.SendTestNotificationResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the test was successful.\n errorMessage:\n type:\n - string\n - \"null\"\n title: error_message\n description: Optional error message if the test failed.\n title: SendTestNotificationResponse\n additionalProperties: false\n description: SendTestNotificationResponse is the response after sending a test notification.\n openstatus.notification.v1.SlackData:\n type: object\n properties:\n webhookUrl:\n type: string\n examples:\n - https://hooks.slack.com/services/T00/B00/xxx\n title: webhook_url\n format: uri\n description: Slack webhook URL.\n title: SlackData\n additionalProperties: false\n description: SlackData contains configuration for Slack notifications.\n openstatus.notification.v1.SmsData:\n type: object\n properties:\n phoneNumber:\n type: string\n examples:\n - \"+14155551234\"\n title: phone_number\n minLength: 1\n description: Phone number to send SMS to.\n title: SmsData\n additionalProperties: false\n description: 'Deprecated: SMS is no longer offered; use WhatsappData. Kept for existing channels.'\n deprecated: true\n openstatus.notification.v1.TelegramData:\n type: object\n properties:\n chatId:\n type: string\n examples:\n - \"-1001234567890\"\n title: chat_id\n minLength: 1\n description: Telegram chat ID.\n title: TelegramData\n additionalProperties: false\n description: TelegramData contains configuration for Telegram notifications.\n openstatus.notification.v1.UpdateNotificationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: Notification ID to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n description: Updated display name.\n data:\n oneOf:\n - $ref: '#/components/schemas/openstatus.notification.v1.NotificationData'\n - type: \"null\"\n title: data\n description: Updated provider-specific configuration.\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: Updated monitor IDs to associate.\n updateMonitorIds:\n type:\n - boolean\n - \"null\"\n title: update_monitor_ids\n description: |-\n Set to true to update monitor associations.\n When true, monitor_ids replaces the existing list (empty clears all).\n When false or unset, monitor_ids is ignored and existing associations are preserved.\n title: UpdateNotificationRequest\n additionalProperties: false\n description: UpdateNotificationRequest is the request to update a notification channel.\n openstatus.notification.v1.UpdateNotificationResponse:\n type: object\n properties:\n notification:\n title: notification\n description: The updated notification channel.\n $ref: '#/components/schemas/openstatus.notification.v1.Notification'\n title: UpdateNotificationResponse\n additionalProperties: false\n description: UpdateNotificationResponse is the response after updating a notification channel.\n openstatus.notification.v1.WebhookData:\n type: object\n properties:\n endpoint:\n type: string\n examples:\n - https://api.example.com/webhooks/openstatus\n title: endpoint\n format: uri\n description: Webhook endpoint URL.\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.notification.v1.WebhookHeader'\n title: headers\n description: Optional custom headers.\n title: WebhookData\n additionalProperties: false\n description: WebhookData contains configuration for custom webhook notifications.\n openstatus.notification.v1.WebhookHeader:\n type: object\n properties:\n key:\n type: string\n title: key\n minLength: 1\n description: Header name.\n value:\n type: string\n title: value\n description: Header value.\n title: WebhookHeader\n additionalProperties: false\n description: WebhookHeader represents a custom header for webhook requests.\n openstatus.notification.v1.WhatsappData:\n type: object\n properties:\n phoneNumber:\n type: string\n title: phone_number\n minLength: 1\n description: Phone number to send WhatsApp messages to.\n title: WhatsappData\n additionalProperties: false\n description: WhatsappData contains configuration for WhatsApp notifications.\n openstatus.private_location.v1.CreatePrivateLocationRequest:\n type: object\n properties:\n name:\n type: string\n examples:\n - eu-west-agent\n title: name\n maxLength: 256\n minLength: 1\n description: Display name for the private location (required, 1-256 characters).\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors this private location should run (optional).\n metadata:\n type: object\n title: metadata\n maxProperties: 20\n additionalProperties:\n type: string\n title: value\n maxLength: 256\n description: User-defined key/value labels (optional, up to 20 entries).\n title: CreatePrivateLocationRequest\n additionalProperties: false\n description: CreatePrivateLocationRequest is the request to create a new private location.\n openstatus.private_location.v1.CreatePrivateLocationRequest.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.CreatePrivateLocationResponse:\n type: object\n properties:\n privateLocation:\n title: private_location\n description: The created private location, including its generated agent token.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocation'\n title: CreatePrivateLocationResponse\n additionalProperties: false\n description: CreatePrivateLocationResponse is the response after creating a private location.\n openstatus.private_location.v1.DeletePrivateLocationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the private location to delete (required).\n title: DeletePrivateLocationRequest\n additionalProperties: false\n description: DeletePrivateLocationRequest is the request to delete a private location.\n openstatus.private_location.v1.DeletePrivateLocationResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeletePrivateLocationResponse\n additionalProperties: false\n description: DeletePrivateLocationResponse is the response after deleting a private location.\n openstatus.private_location.v1.GetPrivateLocationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the private location to retrieve (required).\n title: GetPrivateLocationRequest\n additionalProperties: false\n description: GetPrivateLocationRequest is the request to get a private location by ID.\n openstatus.private_location.v1.GetPrivateLocationResponse:\n type: object\n properties:\n privateLocation:\n title: private_location\n description: The requested private location.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocation'\n title: GetPrivateLocationResponse\n additionalProperties: false\n description: GetPrivateLocationResponse is the response containing the private location.\n openstatus.private_location.v1.ListPrivateLocationsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of private locations to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of private locations to skip for pagination (defaults to 0).\n title: ListPrivateLocationsRequest\n additionalProperties: false\n description: ListPrivateLocationsRequest is the request to list private locations.\n openstatus.private_location.v1.ListPrivateLocationsResponse:\n type: object\n properties:\n privateLocations:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocationSummary'\n title: private_locations\n description: List of private locations.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of private locations in the workspace.\n title: ListPrivateLocationsResponse\n additionalProperties: false\n description: ListPrivateLocationsResponse is the response containing private location summaries.\n openstatus.private_location.v1.PrivateLocation:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the private location.\n name:\n type: string\n title: name\n description: Display name for the private location.\n token:\n type: string\n title: token\n description: |-\n Agent credential. Generated by the server on creation and used by the\n agent to authenticate; treat it as a secret.\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: IDs of monitors this private location runs.\n lastSeenAt:\n type: string\n title: last_seen_at\n description: Last time the agent reported a result (RFC 3339), empty if it never has.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the private location was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the private location was last updated (RFC 3339 format).\n metadata:\n type: object\n title: metadata\n additionalProperties:\n type: string\n title: value\n description: User-defined key/value labels attached to this private location.\n status:\n title: status\n description: Computed health of the agent. Read-only.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocationStatus'\n title: PrivateLocation\n additionalProperties: false\n description: PrivateLocation represents a self-hosted checker agent with full details.\n openstatus.private_location.v1.PrivateLocation.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.PrivateLocationStatus:\n type: string\n title: PrivateLocationStatus\n enum:\n - PRIVATE_LOCATION_STATUS_UNSPECIFIED\n - PRIVATE_LOCATION_STATUS_ACTIVE\n - PRIVATE_LOCATION_STATUS_ERROR\n description: |-\n PrivateLocationStatus is the computed health of a private location, derived\n from the agent heartbeat. Read-only — it cannot be set through the API.\n openstatus.private_location.v1.PrivateLocationSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the private location.\n name:\n type: string\n title: name\n description: Display name for the private location.\n monitorCount:\n type: integer\n title: monitor_count\n format: int32\n description: Number of monitors this private location runs.\n lastSeenAt:\n type: string\n title: last_seen_at\n description: Last time the agent reported a result (RFC 3339), empty if it never has.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the private location was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the private location was last updated (RFC 3339 format).\n status:\n title: status\n description: Computed health of the agent. Read-only.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocationStatus'\n metadata:\n type: object\n title: metadata\n additionalProperties:\n type: string\n title: value\n description: User-defined key/value labels attached to this private location.\n title: PrivateLocationSummary\n additionalProperties: false\n description: |-\n PrivateLocationSummary represents metadata for a private location (used in\n list responses). The agent token is intentionally omitted.\n openstatus.private_location.v1.PrivateLocationSummary.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.UpdatePrivateLocationRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the private location to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n minLength: 1\n description: New display name for the private location (optional).\n monitorIds:\n type: array\n items:\n type: string\n title: monitor_ids\n description: New list of monitor IDs. Only applied when update_monitor_ids is true.\n updateMonitorIds:\n type:\n - boolean\n - \"null\"\n title: update_monitor_ids\n description: |-\n When true, monitor_ids replaces the current associations (an empty list\n clears all). When false or unset, monitor_ids is ignored and existing\n associations are preserved.\n metadata:\n type: object\n title: metadata\n maxProperties: 20\n additionalProperties:\n type: string\n title: value\n maxLength: 256\n description: New key/value labels. Only applied when update_metadata is true.\n updateMetadata:\n type:\n - boolean\n - \"null\"\n title: update_metadata\n description: |-\n When true, metadata replaces the current labels (an empty map clears all).\n When false or unset, metadata is ignored and existing labels are preserved.\n title: UpdatePrivateLocationRequest\n additionalProperties: false\n description: UpdatePrivateLocationRequest is the request to update a private location.\n openstatus.private_location.v1.UpdatePrivateLocationRequest.MetadataEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: MetadataEntry\n additionalProperties: false\n openstatus.private_location.v1.UpdatePrivateLocationResponse:\n type: object\n properties:\n privateLocation:\n title: private_location\n description: The updated private location.\n $ref: '#/components/schemas/openstatus.private_location.v1.PrivateLocation'\n title: UpdatePrivateLocationResponse\n additionalProperties: false\n description: UpdatePrivateLocationResponse is the response after updating a private location.\n openstatus.status_page.v1.AddMonitorComponentRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to add the component to (required).\n monitorId:\n type: string\n title: monitor_id\n minLength: 1\n description: ID of the monitor to associate with this component (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n description: Display name for the component (optional, defaults to monitor name).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the component (optional).\n order:\n type:\n - integer\n - \"null\"\n title: order\n format: int32\n description: Display order of the component (optional).\n groupId:\n type:\n - string\n - \"null\"\n title: group_id\n description: ID of the group to add this component to (optional).\n title: AddMonitorComponentRequest\n additionalProperties: false\n description: AddMonitorComponentRequest is the request to add a monitor-based component to a status page.\n openstatus.status_page.v1.AddMonitorComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The created component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: AddMonitorComponentResponse\n additionalProperties: false\n description: AddMonitorComponentResponse is the response after adding a monitor component.\n openstatus.status_page.v1.AddStaticComponentRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to add the component to (required).\n name:\n type: string\n title: name\n maxLength: 256\n minLength: 1\n description: Display name for the component (required).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the component (optional).\n order:\n type:\n - integer\n - \"null\"\n title: order\n format: int32\n description: Display order of the component (optional).\n groupId:\n type:\n - string\n - \"null\"\n title: group_id\n description: ID of the group to add this component to (optional).\n title: AddStaticComponentRequest\n additionalProperties: false\n description: AddStaticComponentRequest is the request to add a static component to a status page.\n openstatus.status_page.v1.AddStaticComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The created component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: AddStaticComponentResponse\n additionalProperties: false\n description: AddStaticComponentResponse is the response after adding a static component.\n openstatus.status_page.v1.ComponentDayBucket:\n type: object\n properties:\n day:\n type: string\n title: day\n description: Day in RFC 3339 format (UTC midnight).\n count:\n type:\n - integer\n - string\n title: count\n format: int64\n description: Total checks that day.\n ok:\n type:\n - integer\n - string\n title: ok\n format: int64\n description: Successful checks.\n degraded:\n type:\n - integer\n - string\n title: degraded\n format: int64\n description: Degraded checks.\n error:\n type:\n - integer\n - string\n title: error\n format: int64\n description: Failed checks.\n status:\n title: status\n description: Convenience resolved status for the day.\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentDayStatus'\n impact:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_report.v1.PageComponentImpact'\n - type: \"null\"\n title: impact\n description: Worst status-report impact overlapping the day (absent when none).\n title: ComponentDayBucket\n additionalProperties: false\n description: ComponentDayBucket is one day of status data for a component.\n openstatus.status_page.v1.ComponentDayStatus:\n type: string\n title: ComponentDayStatus\n enum:\n - COMPONENT_DAY_STATUS_UNSPECIFIED\n - COMPONENT_DAY_STATUS_OPERATIONAL\n - COMPONENT_DAY_STATUS_DEGRADED\n - COMPONENT_DAY_STATUS_DOWN\n - COMPONENT_DAY_STATUS_MAINTENANCE\n - COMPONENT_DAY_STATUS_EMPTY\n description: ComponentDayStatus is the resolved status of a component on a given day.\n openstatus.status_page.v1.ComponentEvent:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Identifier of the underlying event.\n name:\n type: string\n title: name\n description: Human-readable name (incident \"Downtime\", maintenance / report title).\n type:\n title: type\n description: Kind of event.\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentEventType'\n status:\n title: status\n description: Projected status this event contributes.\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentEventStatus'\n from:\n type: string\n title: from\n description: Start time (RFC 3339).\n to:\n type:\n - string\n - \"null\"\n title: to\n description: End time (RFC 3339); absent while the event is ongoing.\n impact:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_report.v1.PageComponentImpact'\n - type: \"null\"\n title: impact\n description: Worst impact over the event (reports only; absent otherwise).\n title: ComponentEvent\n additionalProperties: false\n description: ComponentEvent is a single incident / maintenance / report affecting a component.\n openstatus.status_page.v1.ComponentEventStatus:\n type: string\n title: ComponentEventStatus\n enum:\n - COMPONENT_EVENT_STATUS_UNSPECIFIED\n - COMPONENT_EVENT_STATUS_OPERATIONAL\n - COMPONENT_EVENT_STATUS_DEGRADED\n - COMPONENT_EVENT_STATUS_DOWN\n - COMPONENT_EVENT_STATUS_MAINTENANCE\n description: ComponentEventStatus is the projected status an event contributes.\n openstatus.status_page.v1.ComponentEventType:\n type: string\n title: ComponentEventType\n enum:\n - COMPONENT_EVENT_TYPE_UNSPECIFIED\n - COMPONENT_EVENT_TYPE_MAINTENANCE\n - COMPONENT_EVENT_TYPE_INCIDENT\n - COMPONENT_EVENT_TYPE_REPORT\n description: ComponentEventType is the kind of timeline event affecting a component.\n openstatus.status_page.v1.ComponentStatus:\n type: object\n properties:\n componentId:\n type: string\n title: component_id\n description: ID of the component.\n status:\n title: status\n description: Current status of the component.\n $ref: '#/components/schemas/openstatus.status_page.v1.OverallStatus'\n title: ComponentStatus\n additionalProperties: false\n description: ComponentStatus represents the status of a single component.\n openstatus.status_page.v1.CreateComponentGroupRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to create the group in (required).\n name:\n type: string\n title: name\n maxLength: 256\n minLength: 1\n description: Display name for the group (required).\n defaultOpen:\n type:\n - boolean\n - \"null\"\n title: default_open\n description: Whether the group should be expanded by default on the status page (optional, defaults to false).\n title: CreateComponentGroupRequest\n additionalProperties: false\n description: CreateComponentGroupRequest is the request to create a new component group.\n openstatus.status_page.v1.CreateComponentGroupResponse:\n type: object\n properties:\n group:\n title: group\n description: The created component group.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: CreateComponentGroupResponse\n additionalProperties: false\n description: CreateComponentGroupResponse is the response after creating a component group.\n openstatus.status_page.v1.CreatePageSubscriptionRequest:\n type: object\n allOf:\n - properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 255\n description: Optional human-readable label.\n componentIds:\n type: array\n items:\n type: string\n title: component_ids\n description: Component scope. Empty = entire page.\n - oneOf:\n - type: object\n properties:\n emailChannel:\n title: email_channel\n $ref: '#/components/schemas/openstatus.status_page.v1.EmailChannel'\n title: email_channel\n required:\n - emailChannel\n - type: object\n properties:\n webhookChannel:\n title: webhook_channel\n $ref: '#/components/schemas/openstatus.status_page.v1.WebhookChannel'\n title: webhook_channel\n required:\n - webhookChannel\n title: CreatePageSubscriptionRequest\n additionalProperties: false\n description: CreatePageSubscriptionRequest is the request to add a vendor-managed subscriber to a status page.\n openstatus.status_page.v1.CreatePageSubscriptionResponse:\n type: object\n properties:\n subscriber:\n title: subscriber\n description: The created subscriber.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageSubscriber'\n title: CreatePageSubscriptionResponse\n additionalProperties: false\n description: CreatePageSubscriptionResponse is the response after creating a vendor-managed subscription.\n openstatus.status_page.v1.CreateStatusPageRequest:\n type: object\n properties:\n title:\n type: string\n examples:\n - Acme Corp Status\n title: title\n maxLength: 256\n minLength: 1\n description: Title of the status page (required).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: Description of the status page (optional).\n slug:\n type: string\n examples:\n - my-status-page\n title: slug\n maxLength: 256\n minLength: 1\n pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$\n description: URL-friendly slug for the status page (required). Must be lowercase alphanumeric with hyphens.\n homepageUrl:\n type:\n - string\n - \"null\"\n examples:\n - https://www.example.com\n title: homepage_url\n description: URL to the homepage (optional).\n contactUrl:\n type:\n - string\n - \"null\"\n title: contact_url\n description: URL to the contact page (optional).\n defaultLocale:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n - type: \"null\"\n title: default_locale\n description: Default locale for the status page (optional, defaults to EN).\n locales:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n title: locales\n description: Enabled locales for the status page (optional).\n icon:\n type:\n - string\n - \"null\"\n title: icon\n maxLength: 1024\n description: Icon URL for the status page (optional).\n customDomain:\n type:\n - string\n - \"null\"\n title: custom_domain\n maxLength: 256\n description: Custom domain for the status page (optional).\n theme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageTheme'\n - type: \"null\"\n title: theme\n description: Visual theme for the status page (optional, defaults to SYSTEM).\n accessType:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageAccessType'\n - type: \"null\"\n title: access_type\n description: Access type for the status page (optional, defaults to PUBLIC).\n password:\n type:\n - string\n - \"null\"\n title: password\n maxLength: 256\n minLength: 1\n description: Password for the status page (required when access_type is PASSWORD_PROTECTED).\n authEmailDomains:\n type: array\n items:\n type: string\n title: auth_email_domains\n description: Email domains allowed to access the page (used when access_type is AUTHENTICATED).\n allowIndex:\n type:\n - boolean\n - \"null\"\n title: allow_index\n description: Whether search engines are allowed to index this status page (optional, defaults to true).\n allowedIpRanges:\n type:\n - string\n - \"null\"\n title: allowed_ip_ranges\n description: Comma-separated IPv4 CIDR ranges (required when access_type is IP_RESTRICTED).\n customTheme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.CustomTheme'\n - type: \"null\"\n title: custom_theme\n description: |-\n Per-mode CSS variable overrides merged over the theme (optional).\n Only supported variable names are accepted. Requires the custom-theme plan feature.\n title: CreateStatusPageRequest\n additionalProperties: false\n description: CreateStatusPageRequest is the request to create a new status page.\n openstatus.status_page.v1.CreateStatusPageResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The created status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n title: CreateStatusPageResponse\n additionalProperties: false\n description: CreateStatusPageResponse is the response after creating a status page.\n openstatus.status_page.v1.CustomTheme:\n type: object\n properties:\n light:\n type: object\n title: light\n additionalProperties:\n type: string\n title: value\n description: 'CSS variable overrides applied in light mode, keyed by variable name (e.g. \"--primary\": \"hsl(24 94% 50%)\").'\n dark:\n type: object\n title: dark\n additionalProperties:\n type: string\n title: value\n description: CSS variable overrides applied in dark mode, keyed by variable name.\n title: CustomTheme\n additionalProperties: false\n description: CustomTheme holds per-mode CSS variable overrides merged over the page theme.\n openstatus.status_page.v1.CustomTheme.DarkEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: DarkEntry\n additionalProperties: false\n openstatus.status_page.v1.CustomTheme.LightEntry:\n type: object\n properties:\n key:\n type: string\n title: key\n value:\n type: string\n title: value\n title: LightEntry\n additionalProperties: false\n openstatus.status_page.v1.DeleteComponentGroupRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component group to delete (required).\n title: DeleteComponentGroupRequest\n additionalProperties: false\n description: DeleteComponentGroupRequest is the request to delete a component group.\n openstatus.status_page.v1.DeleteComponentGroupResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteComponentGroupResponse\n additionalProperties: false\n description: DeleteComponentGroupResponse is the response after deleting a component group.\n openstatus.status_page.v1.DeleteStatusPageRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page to delete (required).\n title: DeleteStatusPageRequest\n additionalProperties: false\n description: DeleteStatusPageRequest is the request to delete a status page.\n openstatus.status_page.v1.DeleteStatusPageResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteStatusPageResponse\n additionalProperties: false\n description: DeleteStatusPageResponse is the response after deleting a status page.\n openstatus.status_page.v1.EmailChannel:\n type: object\n properties:\n email:\n type: string\n title: email\n format: email\n description: Email address of the subscriber (required).\n title: EmailChannel\n additionalProperties: false\n description: EmailChannel carries the email-channel fields for CreatePageSubscription.\n openstatus.status_page.v1.GetOverallStatusRequest:\n type: object\n oneOf:\n - type: object\n properties:\n id:\n type: string\n title: id\n description: ID of the status page.\n title: id\n required:\n - id\n - type: object\n properties:\n slug:\n type: string\n title: slug\n description: Slug of the status page.\n title: slug\n required:\n - slug\n title: GetOverallStatusRequest\n additionalProperties: false\n description: GetOverallStatusRequest is the request to get the overall status of a status page.\n openstatus.status_page.v1.GetOverallStatusResponse:\n type: object\n properties:\n overallStatus:\n title: overall_status\n description: Aggregated status across all components.\n $ref: '#/components/schemas/openstatus.status_page.v1.OverallStatus'\n componentStatuses:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentStatus'\n title: component_statuses\n description: Status of individual components.\n title: GetOverallStatusResponse\n additionalProperties: false\n description: GetOverallStatusResponse is the response containing the overall status and individual component statuses.\n openstatus.status_page.v1.GetPageComponentDailySummaryRequest:\n type: object\n allOf:\n - properties:\n componentIds:\n type: array\n items:\n type: string\n minLength: 1\n title: component_ids\n description: Restrict the result to these component ids (optional, defaults to all components).\n days:\n type:\n - integer\n - \"null\"\n title: days\n maximum: 45\n minimum: 1\n format: int32\n description: Number of days to return (1-45, defaults to 45 if unspecified).\n - oneOf:\n - type: object\n properties:\n id:\n type: string\n title: id\n description: ID of the status page.\n title: id\n required:\n - id\n - type: object\n properties:\n slug:\n type: string\n title: slug\n description: Slug of the status page.\n title: slug\n required:\n - slug\n title: GetPageComponentDailySummaryRequest\n additionalProperties: false\n description: GetPageComponentDailySummaryRequest is the request for per-component daily summaries.\n openstatus.status_page.v1.GetPageComponentDailySummaryResponse:\n type: object\n properties:\n components:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentDailySummary'\n title: components\n description: Per-component daily summaries, in page order.\n title: GetPageComponentDailySummaryResponse\n additionalProperties: false\n description: GetPageComponentDailySummaryResponse is the response containing per-component summaries.\n openstatus.status_page.v1.GetPageComponentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component to retrieve (required).\n title: GetPageComponentRequest\n additionalProperties: false\n description: GetPageComponentRequest is the request to fetch a single component by ID.\n openstatus.status_page.v1.GetPageComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The requested component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: GetPageComponentResponse\n additionalProperties: false\n description: GetPageComponentResponse is the response containing the component.\n openstatus.status_page.v1.GetStatusPageContentRequest:\n type: object\n oneOf:\n - type: object\n properties:\n id:\n type: string\n title: id\n description: ID of the status page.\n title: id\n required:\n - id\n - type: object\n properties:\n slug:\n type: string\n title: slug\n description: Slug of the status page.\n title: slug\n required:\n - slug\n title: GetStatusPageContentRequest\n additionalProperties: false\n description: GetStatusPageContentRequest is the request to get the full content of a status page.\n openstatus.status_page.v1.GetStatusPageContentResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The status page details.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n components:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: components\n description: Components on the status page.\n groups:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: groups\n description: Component groups on the status page.\n statusReports:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: status_reports\n description: Active and recent status reports.\n maintenances:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary'\n title: maintenances\n description: Scheduled maintenances.\n title: GetStatusPageContentResponse\n additionalProperties: false\n description: GetStatusPageContentResponse is the response containing the full status page content.\n openstatus.status_page.v1.GetStatusPageOverviewRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page (required, workspace-scoped).\n title: GetStatusPageOverviewRequest\n additionalProperties: false\n description: GetStatusPageOverviewRequest requests the full overview of a status page by id.\n openstatus.status_page.v1.GetStatusPageOverviewResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The status page details.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n configuration:\n title: configuration\n description: Rich rendering configuration of the page.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageConfiguration'\n components:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: components\n description: Components on the status page.\n groups:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: groups\n description: Component groups on the status page.\n statusReports:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: status_reports\n description: Active and recent status reports.\n maintenances:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary'\n title: maintenances\n description: Scheduled maintenances.\n overallStatus:\n title: overall_status\n description: Aggregated status across all components.\n $ref: '#/components/schemas/openstatus.status_page.v1.OverallStatus'\n componentStatuses:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentStatus'\n title: component_statuses\n description: Status of individual components.\n title: GetStatusPageOverviewResponse\n additionalProperties: false\n description: GetStatusPageOverviewResponse bundles all data for a single status page.\n openstatus.status_page.v1.GetStatusPageRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page to retrieve (required).\n title: GetStatusPageRequest\n additionalProperties: false\n description: GetStatusPageRequest is the request to get a status page by ID.\n openstatus.status_page.v1.GetStatusPageResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The requested status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n title: GetStatusPageResponse\n additionalProperties: false\n description: GetStatusPageResponse is the response containing the status page.\n openstatus.status_page.v1.ListStatusPagesRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of pages to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of pages to skip for pagination (defaults to 0).\n title: ListStatusPagesRequest\n additionalProperties: false\n description: ListStatusPagesRequest is the request to list status pages.\n openstatus.status_page.v1.ListStatusPagesResponse:\n type: object\n properties:\n statusPages:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPageSummary'\n title: status_pages\n description: List of status pages (metadata only).\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of status pages.\n title: ListStatusPagesResponse\n additionalProperties: false\n description: ListStatusPagesResponse is the response containing status page summaries.\n openstatus.status_page.v1.ListSubscribersRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to list subscribers for (required).\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of subscribers to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of subscribers to skip for pagination (defaults to 0).\n includeUnsubscribed:\n type:\n - boolean\n - \"null\"\n title: include_unsubscribed\n description: Whether to include unsubscribed users (defaults to false).\n title: ListSubscribersRequest\n additionalProperties: false\n description: ListSubscribersRequest is the request to list subscribers of a status page.\n openstatus.status_page.v1.ListSubscribersResponse:\n type: object\n properties:\n subscribers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.PageSubscriber'\n title: subscribers\n description: List of subscribers.\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of subscribers matching the filter.\n title: ListSubscribersResponse\n additionalProperties: false\n description: ListSubscribersResponse is the response containing status page subscribers.\n openstatus.status_page.v1.Locale:\n type: string\n title: Locale\n enum:\n - LOCALE_UNSPECIFIED\n - LOCALE_EN\n - LOCALE_FR\n - LOCALE_DE\n - LOCALE_TR\n - LOCALE_HI\n - LOCALE_KO\n - LOCALE_JA\n description: Locale defines the supported languages for a status page.\n openstatus.status_page.v1.OverallStatus:\n type: string\n title: OverallStatus\n enum:\n - OVERALL_STATUS_UNSPECIFIED\n - OVERALL_STATUS_OPERATIONAL\n - OVERALL_STATUS_DEGRADED\n - OVERALL_STATUS_PARTIAL_OUTAGE\n - OVERALL_STATUS_MAJOR_OUTAGE\n - OVERALL_STATUS_MAINTENANCE\n - OVERALL_STATUS_UNKNOWN\n description: OverallStatus represents the aggregated status of all components on a page.\n openstatus.status_page.v1.PageAccessType:\n type: string\n title: PageAccessType\n enum:\n - PAGE_ACCESS_TYPE_UNSPECIFIED\n - PAGE_ACCESS_TYPE_PUBLIC\n - PAGE_ACCESS_TYPE_PASSWORD_PROTECTED\n - PAGE_ACCESS_TYPE_AUTHENTICATED\n - PAGE_ACCESS_TYPE_IP_RESTRICTED\n description: PageAccessType defines who can access the status page.\n openstatus.status_page.v1.PageBarType:\n type: string\n title: PageBarType\n enum:\n - PAGE_BAR_TYPE_UNSPECIFIED\n - PAGE_BAR_TYPE_ABSOLUTE\n - PAGE_BAR_TYPE_MANUAL\n description: PageBarType mirrors page.configuration.type (how the status bar is computed).\n openstatus.status_page.v1.PageComponent:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the component.\n pageId:\n type: string\n title: page_id\n description: ID of the status page this component belongs to.\n name:\n type: string\n title: name\n description: Display name of the component.\n description:\n type: string\n title: description\n description: Description of the component (optional).\n type:\n title: type\n description: Type of the component (monitor or static).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentType'\n monitorId:\n type: string\n title: monitor_id\n description: ID of the monitor if type is MONITOR (optional).\n order:\n type: integer\n title: order\n format: int32\n description: Display order of the component.\n groupId:\n type: string\n title: group_id\n description: ID of the group this component belongs to (optional).\n groupOrder:\n type: integer\n title: group_order\n format: int32\n description: Order within the group if grouped.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the component was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the component was last updated (RFC 3339 format).\n title: PageComponent\n additionalProperties: false\n description: PageComponent represents a component displayed on a status page.\n openstatus.status_page.v1.PageComponentDailySummary:\n type: object\n properties:\n componentId:\n type: string\n title: component_id\n description: ID of the component.\n type:\n title: type\n description: Component type (monitor or static).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentType'\n monitorId:\n type:\n - string\n - \"null\"\n title: monitor_id\n description: Monitor id backing the component (absent for static components).\n name:\n type: string\n title: name\n description: Display name of the component.\n buckets:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentDayBucket'\n title: buckets\n description: Per-day buckets, oldest first.\n events:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.ComponentEvent'\n title: events\n description: Events affecting the component within the window.\n title: PageComponentDailySummary\n additionalProperties: false\n description: PageComponentDailySummary is the per-day buckets + event timeline for one component.\n openstatus.status_page.v1.PageComponentGroup:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the group.\n pageId:\n type: string\n title: page_id\n description: ID of the status page this group belongs to.\n name:\n type: string\n title: name\n description: Display name of the group.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the group was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the group was last updated (RFC 3339 format).\n defaultOpen:\n type: boolean\n title: default_open\n description: Whether the group should be expanded by default on the status page.\n title: PageComponentGroup\n additionalProperties: false\n description: PageComponentGroup represents a group of components on a status page.\n openstatus.status_page.v1.PageComponentType:\n type: string\n title: PageComponentType\n enum:\n - PAGE_COMPONENT_TYPE_UNSPECIFIED\n - PAGE_COMPONENT_TYPE_MONITOR\n - PAGE_COMPONENT_TYPE_STATIC\n description: PageComponentType defines the type of a component on a status page.\n openstatus.status_page.v1.PageConfiguration:\n type: object\n properties:\n metricType:\n title: metric_type\n description: Which metric the status bars represent (configuration.value).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageMetricType'\n barType:\n title: bar_type\n description: How the status bar is computed (configuration.type).\n $ref: '#/components/schemas/openstatus.status_page.v1.PageBarType'\n showUptime:\n type: boolean\n title: show_uptime\n description: Whether to show the uptime percentage (configuration.uptime).\n themeKey:\n type: string\n title: theme_key\n description: |-\n Theme key from the theme store (configuration.theme), e.g. \"default\".\n Free-form string rather than an enum because the theme catalog is dynamic.\n days:\n type: integer\n title: days\n format: int32\n description: 'Number of uptime-bar days rendered on the page (configuration.days): 30 or 45.'\n title: PageConfiguration\n additionalProperties: false\n description: PageConfiguration is the rich rendering config stored in page.configuration.\n openstatus.status_page.v1.PageMetricType:\n type: string\n title: PageMetricType\n enum:\n - PAGE_METRIC_TYPE_UNSPECIFIED\n - PAGE_METRIC_TYPE_DURATION\n - PAGE_METRIC_TYPE_REQUESTS\n - PAGE_METRIC_TYPE_MANUAL\n description: PageMetricType mirrors page.configuration.value (which metric the status bars represent).\n openstatus.status_page.v1.PageSubscriber:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the subscriber.\n pageId:\n type: string\n title: page_id\n description: ID of the status page the user is subscribed to.\n email:\n type: string\n title: email\n description: Email address of the subscriber (empty for webhook channels).\n acceptedAt:\n type: string\n title: accepted_at\n description: Timestamp when the subscription was accepted/confirmed (RFC 3339 format, optional).\n unsubscribedAt:\n type: string\n title: unsubscribed_at\n description: Timestamp when the user unsubscribed (RFC 3339 format, optional).\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the subscription was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the subscription was last updated (RFC 3339 format).\n source:\n title: source\n description: How the subscription was created. Vendor-added rows skip verification.\n $ref: '#/components/schemas/openstatus.status_page.v1.SubscriberSource'\n name:\n type:\n - string\n - \"null\"\n title: name\n description: 'Optional human-readable label (e.g. \"Supabase #incidents\").'\n channelType:\n type: string\n title: channel_type\n description: 'Channel type: \"email\" or \"webhook\".'\n webhookUrl:\n type:\n - string\n - \"null\"\n title: webhook_url\n description: Webhook URL (populated only for channel_type = \"webhook\").\n channelConfig:\n type:\n - string\n - \"null\"\n title: channel_config\n description: JSON-encoded channel config (e.g. custom headers). Populated for webhook channels.\n componentIds:\n type: array\n items:\n type: string\n title: component_ids\n description: IDs of components this subscription is scoped to. Empty = entire page.\n title: PageSubscriber\n additionalProperties: false\n description: PageSubscriber represents a subscriber to a status page.\n openstatus.status_page.v1.PageTheme:\n type: string\n title: PageTheme\n enum:\n - PAGE_THEME_UNSPECIFIED\n - PAGE_THEME_SYSTEM\n - PAGE_THEME_LIGHT\n - PAGE_THEME_DARK\n description: PageTheme defines the visual theme of the status page.\n openstatus.status_page.v1.RemoveComponentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component to remove (required).\n title: RemoveComponentRequest\n additionalProperties: false\n description: RemoveComponentRequest is the request to remove a component from a status page.\n openstatus.status_page.v1.RemoveComponentResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the removal was successful.\n title: RemoveComponentResponse\n additionalProperties: false\n description: RemoveComponentResponse is the response after removing a component.\n openstatus.status_page.v1.StatusPage:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status page.\n title:\n type: string\n title: title\n description: Title of the status page.\n description:\n type: string\n title: description\n description: Description of the status page.\n slug:\n type: string\n examples:\n - acme-corp\n title: slug\n description: URL-friendly slug for the status page.\n customDomain:\n type: string\n examples:\n - status.example.com\n title: custom_domain\n description: Custom domain for the status page (optional).\n published:\n type: boolean\n title: published\n description: Whether the status page is published and visible.\n accessType:\n title: access_type\n description: Access type for the status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageAccessType'\n theme:\n title: theme\n description: Visual theme for the status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageTheme'\n homepageUrl:\n type: string\n title: homepage_url\n description: URL to the homepage (optional).\n contactUrl:\n type: string\n title: contact_url\n description: URL to the contact page (optional).\n icon:\n type: string\n title: icon\n description: Icon URL for the status page (optional).\n createdAt:\n type: string\n examples:\n - \"2024-01-15T09:00:00Z\"\n title: created_at\n description: Timestamp when the page was created (RFC 3339 format).\n updatedAt:\n type: string\n examples:\n - \"2024-06-20T14:30:00Z\"\n title: updated_at\n description: Timestamp when the page was last updated (RFC 3339 format).\n defaultLocale:\n title: default_locale\n description: Default locale for the status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n locales:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n title: locales\n description: Enabled locales for the status page.\n password:\n type: string\n title: password\n description: Password for the status page (only set when access_type is PASSWORD_PROTECTED).\n authEmailDomains:\n type: array\n items:\n type: string\n title: auth_email_domains\n description: Email domains allowed to access the page (only set when access_type is AUTHENTICATED).\n allowIndex:\n type: boolean\n title: allow_index\n description: Whether search engines are allowed to index this status page.\n allowedIpRanges:\n type: string\n title: allowed_ip_ranges\n description: Comma-separated IPv4 CIDR ranges (only set when access_type is IP_RESTRICTED).\n customTheme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.CustomTheme'\n - type: \"null\"\n title: custom_theme\n description: Per-mode CSS variable overrides merged over the theme (only set when configured).\n title: StatusPage\n additionalProperties: false\n description: StatusPage represents a full status page with all details.\n openstatus.status_page.v1.StatusPageSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status page.\n title:\n type: string\n title: title\n description: Title of the status page.\n slug:\n type: string\n title: slug\n description: URL-friendly slug for the status page.\n published:\n type: boolean\n title: published\n description: Whether the status page is published and visible.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the page was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the page was last updated (RFC 3339 format).\n customDomain:\n type: string\n examples:\n - status.example.com\n title: custom_domain\n description: Custom domain for the status page (optional).\n title: StatusPageSummary\n additionalProperties: false\n description: StatusPageSummary represents metadata for a status page (used in list responses).\n openstatus.status_page.v1.SubscribeToPageRequest:\n type: object\n properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to subscribe to (required).\n email:\n type: string\n examples:\n - user@example.com\n title: email\n format: email\n description: Email address to subscribe (required).\n title: SubscribeToPageRequest\n additionalProperties: false\n description: SubscribeToPageRequest is the request to subscribe an email to a status page.\n openstatus.status_page.v1.SubscribeToPageResponse:\n type: object\n properties:\n subscriber:\n title: subscriber\n description: The created subscriber.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageSubscriber'\n title: SubscribeToPageResponse\n additionalProperties: false\n description: SubscribeToPageResponse is the response after subscribing to a status page.\n openstatus.status_page.v1.SubscriberSource:\n type: string\n title: SubscriberSource\n enum:\n - SUBSCRIBER_SOURCE_UNSPECIFIED\n - SUBSCRIBER_SOURCE_SELF_SIGNUP\n - SUBSCRIBER_SOURCE_VENDOR\n - SUBSCRIBER_SOURCE_IMPORT\n description: SubscriberSource indicates how the subscription was created.\n openstatus.status_page.v1.UnsubscribeFromPageRequest:\n type: object\n allOf:\n - properties:\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: ID of the status page to unsubscribe from (required).\n - oneOf:\n - type: object\n properties:\n email:\n type: string\n title: email\n description: Email address to unsubscribe.\n title: email\n required:\n - email\n - type: object\n properties:\n id:\n type: string\n title: id\n description: Subscriber ID.\n title: id\n required:\n - id\n title: UnsubscribeFromPageRequest\n additionalProperties: false\n description: UnsubscribeFromPageRequest is the request to unsubscribe from a status page.\n openstatus.status_page.v1.UnsubscribeFromPageResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the unsubscription was successful.\n title: UnsubscribeFromPageResponse\n additionalProperties: false\n description: UnsubscribeFromPageResponse is the response after unsubscribing from a status page.\n openstatus.status_page.v1.UpdateComponentGroupRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component group to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n minLength: 1\n description: New display name for the group (optional).\n defaultOpen:\n type:\n - boolean\n - \"null\"\n title: default_open\n description: Whether the group should be expanded by default on the status page (optional).\n title: UpdateComponentGroupRequest\n additionalProperties: false\n description: UpdateComponentGroupRequest is the request to update a component group.\n openstatus.status_page.v1.UpdateComponentGroupResponse:\n type: object\n properties:\n group:\n title: group\n description: The updated component group.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponentGroup'\n title: UpdateComponentGroupResponse\n additionalProperties: false\n description: UpdateComponentGroupResponse is the response after updating a component group.\n openstatus.status_page.v1.UpdateComponentRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the component to update (required).\n name:\n type:\n - string\n - \"null\"\n title: name\n maxLength: 256\n description: New display name for the component (optional).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: New description for the component (optional).\n order:\n type:\n - integer\n - \"null\"\n title: order\n format: int32\n description: New display order (optional).\n groupId:\n type:\n - string\n - \"null\"\n title: group_id\n description: New group ID (optional, set to empty string to remove from group).\n groupOrder:\n type:\n - integer\n - \"null\"\n title: group_order\n format: int32\n description: New order within the group (optional).\n title: UpdateComponentRequest\n additionalProperties: false\n description: UpdateComponentRequest is the request to update a component.\n openstatus.status_page.v1.UpdateComponentResponse:\n type: object\n properties:\n component:\n title: component\n description: The updated component.\n $ref: '#/components/schemas/openstatus.status_page.v1.PageComponent'\n title: UpdateComponentResponse\n additionalProperties: false\n description: UpdateComponentResponse is the response after updating a component.\n openstatus.status_page.v1.UpdateStatusPageRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status page to update (required).\n title:\n type:\n - string\n - \"null\"\n title: title\n maxLength: 256\n minLength: 1\n description: New title for the status page (optional).\n description:\n type:\n - string\n - \"null\"\n title: description\n maxLength: 1024\n description: New description for the status page (optional).\n slug:\n type:\n - string\n - \"null\"\n title: slug\n maxLength: 256\n minLength: 1\n pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*$\n description: New slug for the status page (optional).\n homepageUrl:\n type:\n - string\n - \"null\"\n title: homepage_url\n description: New homepage URL (optional).\n contactUrl:\n type:\n - string\n - \"null\"\n title: contact_url\n description: New contact URL (optional).\n defaultLocale:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n - type: \"null\"\n title: default_locale\n description: New default locale for the status page (optional).\n locales:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.Locale'\n title: locales\n description: New enabled locales for the status page (optional).\n icon:\n type:\n - string\n - \"null\"\n title: icon\n maxLength: 1024\n description: New icon URL for the status page (optional).\n customDomain:\n type:\n - string\n - \"null\"\n title: custom_domain\n maxLength: 256\n description: New custom domain (optional).\n theme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageTheme'\n - type: \"null\"\n title: theme\n description: New visual theme (optional).\n accessType:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.PageAccessType'\n - type: \"null\"\n title: access_type\n description: New access type (optional).\n password:\n type:\n - string\n - \"null\"\n title: password\n maxLength: 256\n minLength: 1\n description: New password (optional, required when access_type is PASSWORD_PROTECTED).\n authEmailDomains:\n type: array\n items:\n type: string\n title: auth_email_domains\n description: New email domains (optional, required when access_type is AUTHENTICATED).\n allowIndex:\n type:\n - boolean\n - \"null\"\n title: allow_index\n description: Whether search engines are allowed to index this status page (optional).\n allowedIpRanges:\n type:\n - string\n - \"null\"\n title: allowed_ip_ranges\n description: Comma-separated IPv4 CIDR ranges (required when access_type is IP_RESTRICTED).\n customTheme:\n oneOf:\n - $ref: '#/components/schemas/openstatus.status_page.v1.CustomTheme'\n - type: \"null\"\n title: custom_theme\n description: |-\n New per-mode CSS variable overrides (optional). Omit to keep the current\n value; send an empty message to clear. Requires the custom-theme plan feature.\n title: UpdateStatusPageRequest\n additionalProperties: false\n description: UpdateStatusPageRequest is the request to update a status page.\n openstatus.status_page.v1.UpdateStatusPageResponse:\n type: object\n properties:\n statusPage:\n title: status_page\n description: The updated status page.\n $ref: '#/components/schemas/openstatus.status_page.v1.StatusPage'\n title: UpdateStatusPageResponse\n additionalProperties: false\n description: UpdateStatusPageResponse is the response after updating a status page.\n openstatus.status_page.v1.WebhookChannel:\n type: object\n properties:\n webhookUrl:\n type: string\n title: webhook_url\n maxLength: 2048\n minLength: 1\n format: uri\n description: Webhook URL (required). Payload flavor (Slack / Discord / generic) is auto-detected from the URL prefix.\n headers:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_page.v1.WebhookChannelHeader'\n title: headers\n description: Optional custom HTTP headers attached to every dispatch.\n title: WebhookChannel\n additionalProperties: false\n description: WebhookChannel carries the webhook-channel fields for CreatePageSubscription.\n openstatus.status_page.v1.WebhookChannelHeader:\n type: object\n properties:\n key:\n type: string\n title: key\n minLength: 1\n value:\n type: string\n title: value\n title: WebhookChannelHeader\n additionalProperties: false\n description: WebhookChannelHeader is a single custom HTTP header.\n openstatus.status_report.v1.AddStatusReportUpdateRequest:\n type: object\n properties:\n statusReportId:\n type: string\n title: status_report_id\n minLength: 1\n description: ID of the status report to update (required).\n status:\n title: status\n description: New status for the report (required).\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n message:\n type: string\n title: message\n minLength: 1\n description: Message describing what changed (required).\n date:\n type:\n - string\n - \"null\"\n title: date\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: Optional date for the update (RFC 3339 format). Defaults to current time if not provided.\n notify:\n type:\n - boolean\n - \"null\"\n title: notify\n description: Whether to notify subscribers about this update (optional, defaults to false).\n componentImpacts:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.ComponentImpact'\n title: component_impacts\n description: |-\n Per-component impacts this update sets (optional). Components named here\n are added to the report's affected set; omitted components keep their\n prior impact.\n title: AddStatusReportUpdateRequest\n additionalProperties: false\n description: AddStatusReportUpdateRequest is the request to add a new update to a status report.\n openstatus.status_report.v1.AddStatusReportUpdateResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The updated status report with the new update included.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: AddStatusReportUpdateResponse\n additionalProperties: false\n description: AddStatusReportUpdateResponse is the response after adding an update to a status report.\n openstatus.status_report.v1.ComponentImpact:\n type: object\n properties:\n pageComponentId:\n type: string\n title: page_component_id\n description: ID of the affected page component.\n impact:\n title: impact\n description: Impact set for the component.\n $ref: '#/components/schemas/openstatus.status_report.v1.PageComponentImpact'\n title: ComponentImpact\n additionalProperties: false\n description: ComponentImpact pairs a page component with the impact an update set for it.\n openstatus.status_report.v1.CreateStatusReportRequest:\n type: object\n properties:\n title:\n type: string\n examples:\n - API Degradation Investigation\n title: title\n minLength: 1\n description: Title of the status report (required).\n status:\n title: status\n description: Initial status (required).\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n message:\n type: string\n examples:\n - We are investigating reports of increased API latency.\n title: message\n minLength: 1\n description: Initial message describing the incident (required).\n date:\n type: string\n examples:\n - \"2024-03-15T10:30:00Z\"\n title: date\n pattern: ^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$\n description: Date when the event occurred (RFC 3339 format, required).\n pageId:\n type: string\n title: page_id\n minLength: 1\n description: Page ID to associate with this report (required).\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: Page component IDs to associate with this report (optional).\n notify:\n type:\n - boolean\n - \"null\"\n title: notify\n description: Whether to notify subscribers about this status report (optional, defaults to false).\n componentImpacts:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.ComponentImpact'\n title: component_impacts\n description: |-\n Per-component impacts set by the initial update (optional). When provided,\n the named components are added to the report's affected set. Omitting this\n field creates a legacy report without impact tracking.\n incidentId:\n type:\n - string\n - \"null\"\n title: incident_id\n minLength: 1\n description: |-\n ID of an incident to link the report to (optional). Linked in the same\n step: a closed incident, or one already linked to a report, fails the create.\n title: CreateStatusReportRequest\n additionalProperties: false\n description: CreateStatusReportRequest is the request to create a new status report.\n openstatus.status_report.v1.CreateStatusReportResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The created status report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: CreateStatusReportResponse\n additionalProperties: false\n description: CreateStatusReportResponse is the response after creating a status report.\n openstatus.status_report.v1.DeleteStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status report to delete (required).\n title: DeleteStatusReportRequest\n additionalProperties: false\n description: DeleteStatusReportRequest is the request to delete a status report.\n openstatus.status_report.v1.DeleteStatusReportResponse:\n type: object\n properties:\n success:\n type: boolean\n title: success\n description: Whether the deletion was successful.\n title: DeleteStatusReportResponse\n additionalProperties: false\n description: DeleteStatusReportResponse is the response after deleting a status report.\n openstatus.status_report.v1.GetStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status report to retrieve (required).\n title: GetStatusReportRequest\n additionalProperties: false\n description: GetStatusReportRequest is the request to get a status report by ID.\n openstatus.status_report.v1.GetStatusReportResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The requested status report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: GetStatusReportResponse\n additionalProperties: false\n description: GetStatusReportResponse is the response containing the status report with its full update timeline.\n openstatus.status_report.v1.ListStatusReportsRequest:\n type: object\n properties:\n limit:\n type:\n - integer\n - \"null\"\n title: limit\n maximum: 100\n minimum: 1\n format: int32\n description: Maximum number of reports to return (1-100, defaults to 50).\n offset:\n type:\n - integer\n - \"null\"\n title: offset\n minimum: 0\n format: int32\n description: Number of reports to skip for pagination (defaults to 0).\n statuses:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n title: statuses\n description: Filter by status (optional). If empty, returns all statuses.\n title: ListStatusReportsRequest\n additionalProperties: false\n description: ListStatusReportsRequest is the request to list status reports.\n openstatus.status_report.v1.ListStatusReportsResponse:\n type: object\n properties:\n statusReports:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportSummary'\n title: status_reports\n description: List of status reports (metadata only, use GetStatusReport for full details).\n totalSize:\n type: integer\n title: total_size\n format: int32\n description: Total number of reports matching the filter.\n title: ListStatusReportsResponse\n additionalProperties: false\n description: ListStatusReportsResponse is the response containing status report summaries.\n openstatus.status_report.v1.PageComponentImpact:\n type: string\n title: PageComponentImpact\n enum:\n - PAGE_COMPONENT_IMPACT_UNSPECIFIED\n - PAGE_COMPONENT_IMPACT_OPERATIONAL\n - PAGE_COMPONENT_IMPACT_DEGRADED_PERFORMANCE\n - PAGE_COMPONENT_IMPACT_PARTIAL_OUTAGE\n - PAGE_COMPONENT_IMPACT_MAJOR_OUTAGE\n description: |-\n PageComponentImpact is the per-component impact a status report update sets.\n UNSPECIFIED means the caller doesn't speak impact (legacy) — it is NOT operational.\n openstatus.status_report.v1.StatusReport:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status report.\n status:\n title: status\n description: Current status of the report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n title:\n type: string\n title: title\n description: Title of the status report.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n updates:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportUpdate'\n title: updates\n description: Timeline of updates for this report (only included in GetStatusReport).\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the report was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the report was last updated (RFC 3339 format).\n incidentId:\n type:\n - string\n - \"null\"\n title: incident_id\n description: ID of the incident this report communicates (unset when not linked).\n title: StatusReport\n additionalProperties: false\n description: StatusReport represents an incident or maintenance report with full details.\n openstatus.status_report.v1.StatusReportStatus:\n type: string\n title: StatusReportStatus\n enum:\n - STATUS_REPORT_STATUS_UNSPECIFIED\n - STATUS_REPORT_STATUS_INVESTIGATING\n - STATUS_REPORT_STATUS_IDENTIFIED\n - STATUS_REPORT_STATUS_MONITORING\n - STATUS_REPORT_STATUS_RESOLVED\n description: StatusReportStatus represents the current state of a status report.\n openstatus.status_report.v1.StatusReportSummary:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the status report.\n status:\n title: status\n description: Current status of the report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n title:\n type: string\n title: title\n description: Title of the status report.\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: IDs of affected page components.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the report was created (RFC 3339 format).\n updatedAt:\n type: string\n title: updated_at\n description: Timestamp when the report was last updated (RFC 3339 format).\n incidentId:\n type:\n - string\n - \"null\"\n title: incident_id\n description: ID of the incident this report communicates (unset when not linked).\n title: StatusReportSummary\n additionalProperties: false\n description: StatusReportSummary represents metadata for a status report (used in list responses).\n openstatus.status_report.v1.StatusReportUpdate:\n type: object\n properties:\n id:\n type: string\n title: id\n description: Unique identifier for the update.\n status:\n title: status\n description: Status at the time of this update.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus'\n date:\n type: string\n title: date\n description: Timestamp when this update occurred (RFC 3339 format).\n message:\n type: string\n title: message\n description: Message describing the update.\n createdAt:\n type: string\n title: created_at\n description: Timestamp when the update was created (RFC 3339 format).\n componentImpacts:\n type: array\n items:\n $ref: '#/components/schemas/openstatus.status_report.v1.ComponentImpact'\n title: component_impacts\n description: Per-component impacts this update set (empty for legacy reports).\n title: StatusReportUpdate\n additionalProperties: false\n description: StatusReportUpdate represents a single update entry in a status report timeline.\n openstatus.status_report.v1.UpdateStatusReportRequest:\n type: object\n properties:\n id:\n type: string\n title: id\n minLength: 1\n description: ID of the status report to update (required).\n title:\n type:\n - string\n - \"null\"\n title: title\n description: New title for the report (optional).\n pageComponentIds:\n type: array\n items:\n type: string\n title: page_component_ids\n description: New list of page component IDs (optional, replaces existing list).\n updatePageComponentIds:\n type:\n - boolean\n - \"null\"\n title: update_page_component_ids\n description: |-\n Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved.\n title: UpdateStatusReportRequest\n additionalProperties: false\n description: UpdateStatusReportRequest is the request to update a status report's metadata.\n openstatus.status_report.v1.UpdateStatusReportResponse:\n type: object\n properties:\n statusReport:\n title: status_report\n description: The updated status report.\n $ref: '#/components/schemas/openstatus.status_report.v1.StatusReport'\n title: UpdateStatusReportResponse\n additionalProperties: false\n description: UpdateStatusReportResponse is the response after updating a status report.\nsecurity:\n - ApiKeyAuth: []\ntags:\n - name: MonitorService\n description: |\n Create, update, delete, and query monitors. Supports HTTP, TCP, and DNS monitor types\n with configurable check intervals, regions, assertions, and alerting thresholds.\n - name: StatusPageService\n description: |\n Manage public status pages with components, component groups, and email subscribers.\n Includes endpoints for retrieving full page content and aggregated status.\n - name: NotificationService\n description: |\n Configure notification channels (Slack, Discord, PagerDuty, email, webhooks, etc.)\n and associate them with monitors. Supports 12 notification providers.\n - name: StatusReportService\n description: |\n Create and manage public status reports with status updates. Reports follow a lifecycle:\n investigating -> identified -> monitoring -> resolved.\n - name: IncidentService\n description: |\n Declare and run incidents: lifecycle (open -> mitigated -> resolved, or canceled),\n timeline notes, a linked status report, and the postmortem.\n - name: MaintenanceService\n description: |\n Schedule maintenance windows for status page components. Subscribers can be\n notified automatically when maintenance is created.\n - name: HealthService\n description: Health check endpoint for load balancer probes. No authentication required.\n - name: PrivateLocationService\n description: |-\n PrivateLocationService provides CRUD operations for private locations —\n self-hosted checker agents that run monitors from your own network.\npaths:\n /rpc/openstatus.health.v1.HealthService/Check:\n get:\n tags:\n - HealthService\n summary: Check\n description: Check returns the current serving status of the service.\n operationId: HealthService_Check.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckResponse'\n post:\n tags:\n - HealthService\n summary: Check\n description: Check returns the current serving status of the service.\n operationId: HealthService_Check\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.health.v1.CheckResponse'\n /rpc/openstatus.incident.v1.IncidentService/AddIncidentNote:\n post:\n tags:\n - IncidentService\n summary: AddIncidentNote\n description: AddIncidentNote appends a note to the incident timeline.\n operationId: IncidentService_AddIncidentNote\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.AddIncidentNoteRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.AddIncidentNoteResponse'\n /rpc/openstatus.incident.v1.IncidentService/ApprovePostmortem:\n post:\n tags:\n - IncidentService\n summary: ApprovePostmortem\n description: Approves the draft postmortem of a resolved incident. With close set to true the incident is closed in the same step. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this.\n operationId: IncidentService_ApprovePostmortem\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.ApprovePostmortemRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.ApprovePostmortemResponse'\n /rpc/openstatus.incident.v1.IncidentService/CloseIncident:\n post:\n tags:\n - IncidentService\n summary: CloseIncident\n description: Closes a resolved incident, after which only its postmortem can change. Requires an approved postmortem, or skip_postmortem set to true. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this.\n operationId: IncidentService_CloseIncident\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.CloseIncidentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.CloseIncidentResponse'\n /rpc/openstatus.incident.v1.IncidentService/DeclareIncident:\n post:\n tags:\n - IncidentService\n summary: DeclareIncident\n description: Declares a new incident in the open status. The commander is set by email and must be a member of the workspace; without one the incident is unassigned. A newly assigned commander is emailed unless they own the API key. When open_slack_channel is true and Slack is connected, a dedicated channel is created and the team invited. started_at defaults to now and can be set in the past for a retroactive declare.\n operationId: IncidentService_DeclareIncident\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.DeclareIncidentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.DeclareIncidentResponse'\n /rpc/openstatus.incident.v1.IncidentService/DeleteIncident:\n post:\n tags:\n - IncidentService\n summary: DeleteIncident\n description: Deletes an incident declared by mistake. Only allowed while the incident is open and was never mitigated, resolved or closed (Incident.deletable); cancel it otherwise. Allowed for workspace owners and admins only. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this.\n operationId: IncidentService_DeleteIncident\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.DeleteIncidentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.DeleteIncidentResponse'\n /rpc/openstatus.incident.v1.IncidentService/GetIncident:\n get:\n tags:\n - IncidentService\n summary: GetIncident\n description: GetIncident retrieves an incident by ID, including its timeline.\n operationId: IncidentService_GetIncident.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentResponse'\n post:\n tags:\n - IncidentService\n summary: GetIncident\n description: GetIncident retrieves an incident by ID, including its timeline.\n operationId: IncidentService_GetIncident\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentResponse'\n /rpc/openstatus.incident.v1.IncidentService/GetPostmortem:\n get:\n tags:\n - IncidentService\n summary: GetPostmortem\n description: GetPostmortem retrieves the postmortem of an incident, if any.\n operationId: IncidentService_GetPostmortem.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemResponse'\n post:\n tags:\n - IncidentService\n summary: GetPostmortem\n description: GetPostmortem retrieves the postmortem of an incident, if any.\n operationId: IncidentService_GetPostmortem\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemResponse'\n /rpc/openstatus.incident.v1.IncidentService/LinkStatusReport:\n post:\n tags:\n - IncidentService\n summary: LinkStatusReport\n description: LinkStatusReport links a public status report to the incident.\n operationId: IncidentService_LinkStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.LinkStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.LinkStatusReportResponse'\n /rpc/openstatus.incident.v1.IncidentService/ListIncidents:\n get:\n tags:\n - IncidentService\n summary: ListIncidents\n description: ListIncidents returns the incidents of the workspace (metadata only), open first.\n operationId: IncidentService_ListIncidents.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsResponse'\n post:\n tags:\n - IncidentService\n summary: ListIncidents\n description: ListIncidents returns the incidents of the workspace (metadata only), open first.\n operationId: IncidentService_ListIncidents\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsResponse'\n /rpc/openstatus.incident.v1.IncidentService/SetIncidentStatus:\n post:\n tags:\n - IncidentService\n summary: SetIncidentStatus\n description: 'Moves an incident to a new status. Allowed transitions: open -> mitigated, resolved or canceled; mitigated -> resolved, open or canceled; resolved -> open. Canceled is terminal and closes the incident. The same status, a forbidden transition or a closed incident fail with failed_precondition; Incident.allowed_transitions lists what is valid. A linked status report is never updated: post the public update with StatusReportService.AddStatusReportUpdate.'\n operationId: IncidentService_SetIncidentStatus\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.SetIncidentStatusRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.SetIncidentStatusResponse'\n /rpc/openstatus.incident.v1.IncidentService/UnlinkStatusReport:\n post:\n tags:\n - IncidentService\n summary: UnlinkStatusReport\n description: UnlinkStatusReport removes the link to the incident's status report.\n operationId: IncidentService_UnlinkStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.UnlinkStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.UnlinkStatusReportResponse'\n /rpc/openstatus.incident.v1.IncidentService/UpdateIncident:\n post:\n tags:\n - IncidentService\n summary: UpdateIncident\n description: UpdateIncident edits the title, severity, summary, commander or start time of an incident.\n operationId: IncidentService_UpdateIncident\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.UpdateIncidentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.UpdateIncidentResponse'\n /rpc/openstatus.incident.v1.IncidentService/UpdatePostmortem:\n post:\n tags:\n - IncidentService\n summary: UpdatePostmortem\n description: UpdatePostmortem creates or replaces the postmortem body of a resolved incident.\n operationId: IncidentService_UpdatePostmortem\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.UpdatePostmortemRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.incident.v1.UpdatePostmortemResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/CreateMaintenance:\n post:\n tags:\n - MaintenanceService\n summary: CreateMaintenance\n description: CreateMaintenance creates a new maintenance window.\n operationId: MaintenanceService_CreateMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.CreateMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.CreateMaintenanceResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/DeleteMaintenance:\n post:\n tags:\n - MaintenanceService\n summary: DeleteMaintenance\n description: DeleteMaintenance removes a maintenance window.\n operationId: MaintenanceService_DeleteMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.DeleteMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.DeleteMaintenanceResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/GetMaintenance:\n get:\n tags:\n - MaintenanceService\n summary: GetMaintenance\n description: GetMaintenance retrieves a specific maintenance window by ID.\n operationId: MaintenanceService_GetMaintenance.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceResponse'\n post:\n tags:\n - MaintenanceService\n summary: GetMaintenance\n description: GetMaintenance retrieves a specific maintenance window by ID.\n operationId: MaintenanceService_GetMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.GetMaintenanceResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/ListMaintenances:\n get:\n tags:\n - MaintenanceService\n summary: ListMaintenances\n description: ListMaintenances returns all maintenance windows for the workspace.\n operationId: MaintenanceService_ListMaintenances.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesResponse'\n post:\n tags:\n - MaintenanceService\n summary: ListMaintenances\n description: ListMaintenances returns all maintenance windows for the workspace.\n operationId: MaintenanceService_ListMaintenances\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.ListMaintenancesResponse'\n /rpc/openstatus.maintenance.v1.MaintenanceService/UpdateMaintenance:\n post:\n tags:\n - MaintenanceService\n summary: UpdateMaintenance\n description: UpdateMaintenance updates a maintenance window.\n operationId: MaintenanceService_UpdateMaintenance\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.UpdateMaintenanceRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.maintenance.v1.UpdateMaintenanceResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateDNSMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateDNSMonitor\n description: CreateDNSMonitor creates a new DNS monitor.\n operationId: MonitorService_CreateDNSMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateDNSMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateDNSMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateGRPCMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateGRPCMonitor\n description: CreateGRPCMonitor creates a new gRPC health check monitor.\n operationId: MonitorService_CreateGRPCMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateGRPCMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateGRPCMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateHTTPMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateHTTPMonitor\n description: Creates a new HTTP monitor in the authenticated workspace. Configure the target URL, HTTP method, request headers and body, response assertions (status code, body content, headers), check periodicity, geographic regions, and optional OpenTelemetry export. The monitor starts checking immediately if set to active.\n operationId: MonitorService_CreateHTTPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateHTTPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateHTTPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateICMPMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateICMPMonitor\n description: CreateICMPMonitor creates a new ICMP monitor.\n operationId: MonitorService_CreateICMPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateICMPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateICMPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/CreateTCPMonitor:\n post:\n tags:\n - MonitorService\n summary: CreateTCPMonitor\n description: CreateTCPMonitor creates a new TCP monitor.\n operationId: MonitorService_CreateTCPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateTCPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.CreateTCPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/DeleteMonitor:\n post:\n tags:\n - MonitorService\n summary: DeleteMonitor\n description: DeleteMonitor removes a monitor.\n operationId: MonitorService_DeleteMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.DeleteMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.DeleteMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitor:\n get:\n tags:\n - MonitorService\n summary: GetMonitor\n description: |-\n GetMonitor returns a single monitor by ID within the authenticated workspace.\n Returns the monitor configuration (HTTP, TCP, DNS, ICMP, or gRPC) using the MonitorConfig oneof type.\n operationId: MonitorService_GetMonitor.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitor\n description: |-\n GetMonitor returns a single monitor by ID within the authenticated workspace.\n Returns the monitor configuration (HTTP, TCP, DNS, ICMP, or gRPC) using the MonitorConfig oneof type.\n operationId: MonitorService_GetMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitorHTTPResponseLog:\n get:\n tags:\n - MonitorService\n summary: GetMonitorHTTPResponseLog\n description: GetMonitorHTTPResponseLog returns one response log for an HTTP monitor.\n operationId: MonitorService_GetMonitorHTTPResponseLog.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitorHTTPResponseLog\n description: GetMonitorHTTPResponseLog returns one response log for an HTTP monitor.\n operationId: MonitorService_GetMonitorHTTPResponseLog\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitorStatus:\n get:\n tags:\n - MonitorService\n summary: GetMonitorStatus\n description: GetMonitorStatus returns the current status of all regions for a monitor.\n operationId: MonitorService_GetMonitorStatus.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitorStatus\n description: GetMonitorStatus returns the current status of all regions for a monitor.\n operationId: MonitorService_GetMonitorStatus\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorStatusResponse'\n /rpc/openstatus.monitor.v1.MonitorService/GetMonitorSummary:\n get:\n tags:\n - MonitorService\n summary: GetMonitorSummary\n description: Returns aggregated metrics for a monitor including latency percentiles (p50, p75, p90, p95, p99), request counts by status (successful, degraded, failed), and the timestamp of the last check. Metrics can be scoped to a time range (1 day, 7 days, or 14 days) and filtered by specific regions.\n operationId: MonitorService_GetMonitorSummary.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryResponse'\n post:\n tags:\n - MonitorService\n summary: GetMonitorSummary\n description: Returns aggregated metrics for a monitor including latency percentiles (p50, p75, p90, p95, p99), request counts by status (successful, degraded, failed), and the timestamp of the last check. Metrics can be scoped to a time range (1 day, 7 days, or 14 days) and filtered by specific regions.\n operationId: MonitorService_GetMonitorSummary\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.GetMonitorSummaryResponse'\n /rpc/openstatus.monitor.v1.MonitorService/ListMonitorHTTPResponseLogs:\n get:\n tags:\n - MonitorService\n summary: ListMonitorHTTPResponseLogs\n description: ListMonitorHTTPResponseLogs returns paginated response logs for an HTTP monitor from the 14-day HTTP response-log window.\n operationId: MonitorService_ListMonitorHTTPResponseLogs.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse'\n post:\n tags:\n - MonitorService\n summary: ListMonitorHTTPResponseLogs\n description: ListMonitorHTTPResponseLogs returns paginated response logs for an HTTP monitor from the 14-day HTTP response-log window.\n operationId: MonitorService_ListMonitorHTTPResponseLogs\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse'\n /rpc/openstatus.monitor.v1.MonitorService/ListMonitors:\n get:\n tags:\n - MonitorService\n summary: ListMonitors\n description: ListMonitors returns a paginated list of all monitors in the workspace.\n operationId: MonitorService_ListMonitors.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsResponse'\n post:\n tags:\n - MonitorService\n summary: ListMonitors\n description: ListMonitors returns a paginated list of all monitors in the workspace.\n operationId: MonitorService_ListMonitors\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.ListMonitorsResponse'\n /rpc/openstatus.monitor.v1.MonitorService/TriggerMonitor:\n post:\n tags:\n - MonitorService\n summary: TriggerMonitor\n description: Manually triggers an immediate check for the specified monitor across all configured regions. This operation is rate-limited under the synthetic-checks quota. A monitor run record is created and the check is dispatched to the checker service.\n operationId: MonitorService_TriggerMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.TriggerMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.TriggerMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateDNSMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateDNSMonitor\n description: UpdateDNSMonitor updates an existing DNS monitor.\n operationId: MonitorService_UpdateDNSMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateDNSMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateDNSMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateGRPCMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateGRPCMonitor\n description: UpdateGRPCMonitor updates an existing gRPC monitor.\n operationId: MonitorService_UpdateGRPCMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateGRPCMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateGRPCMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateHTTPMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateHTTPMonitor\n description: UpdateHTTPMonitor updates an existing HTTP monitor.\n operationId: MonitorService_UpdateHTTPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateHTTPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateHTTPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateICMPMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateICMPMonitor\n description: UpdateICMPMonitor updates an existing ICMP monitor.\n operationId: MonitorService_UpdateICMPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateICMPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateICMPMonitorResponse'\n /rpc/openstatus.monitor.v1.MonitorService/UpdateTCPMonitor:\n post:\n tags:\n - MonitorService\n summary: UpdateTCPMonitor\n description: UpdateTCPMonitor updates an existing TCP monitor.\n operationId: MonitorService_UpdateTCPMonitor\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateTCPMonitorRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.monitor.v1.UpdateTCPMonitorResponse'\n /rpc/openstatus.notification.v1.NotificationService/CheckNotificationLimit:\n get:\n tags:\n - NotificationService\n summary: CheckNotificationLimit\n description: CheckNotificationLimit checks if the workspace has reached its notification limit.\n operationId: NotificationService_CheckNotificationLimit.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitResponse'\n post:\n tags:\n - NotificationService\n summary: CheckNotificationLimit\n description: CheckNotificationLimit checks if the workspace has reached its notification limit.\n operationId: NotificationService_CheckNotificationLimit\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CheckNotificationLimitResponse'\n /rpc/openstatus.notification.v1.NotificationService/CreateNotification:\n post:\n tags:\n - NotificationService\n summary: CreateNotification\n description: CreateNotification creates a new notification channel.\n operationId: NotificationService_CreateNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CreateNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.CreateNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/DeleteNotification:\n post:\n tags:\n - NotificationService\n summary: DeleteNotification\n description: DeleteNotification removes a notification channel.\n operationId: NotificationService_DeleteNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.DeleteNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.DeleteNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/GetNotification:\n get:\n tags:\n - NotificationService\n summary: GetNotification\n description: GetNotification retrieves a notification channel by ID.\n operationId: NotificationService_GetNotification.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationResponse'\n post:\n tags:\n - NotificationService\n summary: GetNotification\n description: GetNotification retrieves a notification channel by ID.\n operationId: NotificationService_GetNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.GetNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/ListNotifications:\n get:\n tags:\n - NotificationService\n summary: ListNotifications\n description: ListNotifications returns a list of notification channels.\n operationId: NotificationService_ListNotifications.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsResponse'\n post:\n tags:\n - NotificationService\n summary: ListNotifications\n description: ListNotifications returns a list of notification channels.\n operationId: NotificationService_ListNotifications\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.ListNotificationsResponse'\n /rpc/openstatus.notification.v1.NotificationService/SendTestNotification:\n post:\n tags:\n - NotificationService\n summary: SendTestNotification\n description: Sends a test notification to the specified provider to verify that the configuration is correct. This does not require an existing notification channel - just provide the provider type and its configuration data. Returns success status and an error message if the test failed.\n operationId: NotificationService_SendTestNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.SendTestNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.SendTestNotificationResponse'\n /rpc/openstatus.notification.v1.NotificationService/UpdateNotification:\n post:\n tags:\n - NotificationService\n summary: UpdateNotification\n description: UpdateNotification updates an existing notification channel.\n operationId: NotificationService_UpdateNotification\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.UpdateNotificationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.notification.v1.UpdateNotificationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/CreatePrivateLocation:\n post:\n tags:\n - PrivateLocationService\n summary: CreatePrivateLocation\n description: Creates a private location. The agent token is generated by the server and returned in the response - it cannot be supplied by the caller. Use the token to configure the agent so it can pull its monitors and report results.\n operationId: PrivateLocationService_CreatePrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.CreatePrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.CreatePrivateLocationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/DeletePrivateLocation:\n post:\n tags:\n - PrivateLocationService\n summary: DeletePrivateLocation\n description: DeletePrivateLocation removes a private location.\n operationId: PrivateLocationService_DeletePrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.DeletePrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.DeletePrivateLocationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/GetPrivateLocation:\n get:\n tags:\n - PrivateLocationService\n summary: GetPrivateLocation\n description: |-\n GetPrivateLocation retrieves a single private location by ID, including\n its agent token.\n operationId: PrivateLocationService_GetPrivateLocation.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationResponse'\n post:\n tags:\n - PrivateLocationService\n summary: GetPrivateLocation\n description: |-\n GetPrivateLocation retrieves a single private location by ID, including\n its agent token.\n operationId: PrivateLocationService_GetPrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.GetPrivateLocationResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/ListPrivateLocations:\n get:\n tags:\n - PrivateLocationService\n summary: ListPrivateLocations\n description: |-\n ListPrivateLocations returns a paginated list of private location\n summaries. Agent tokens are not included - use GetPrivateLocation.\n operationId: PrivateLocationService_ListPrivateLocations.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsResponse'\n post:\n tags:\n - PrivateLocationService\n summary: ListPrivateLocations\n description: |-\n ListPrivateLocations returns a paginated list of private location\n summaries. Agent tokens are not included - use GetPrivateLocation.\n operationId: PrivateLocationService_ListPrivateLocations\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.ListPrivateLocationsResponse'\n /rpc/openstatus.private_location.v1.PrivateLocationService/UpdatePrivateLocation:\n post:\n tags:\n - PrivateLocationService\n summary: UpdatePrivateLocation\n description: UpdatePrivateLocation updates a private location.\n operationId: PrivateLocationService_UpdatePrivateLocation\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.UpdatePrivateLocationRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.private_location.v1.UpdatePrivateLocationResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/AddMonitorComponent:\n post:\n tags:\n - StatusPageService\n summary: AddMonitorComponent\n description: AddMonitorComponent adds a monitor-based component to a status page.\n operationId: StatusPageService_AddMonitorComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddMonitorComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddMonitorComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/AddStaticComponent:\n post:\n tags:\n - StatusPageService\n summary: AddStaticComponent\n description: AddStaticComponent adds a static component to a status page.\n operationId: StatusPageService_AddStaticComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddStaticComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.AddStaticComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/CreateComponentGroup:\n post:\n tags:\n - StatusPageService\n summary: CreateComponentGroup\n description: CreateComponentGroup creates a new component group.\n operationId: StatusPageService_CreateComponentGroup\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateComponentGroupRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateComponentGroupResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/CreatePageSubscription:\n post:\n tags:\n - StatusPageService\n summary: 'CreatePageSubscription: operator-added subscriber (email or webhook), no verification.'\n description: Operator-added subscriber (email or webhook) with no verification flow — the partner starts receiving notifications immediately. Supports Slack and Discord webhook URLs in addition to email. A management token is still generated so the subscriber can self-manage (update scope, unsubscribe) without operator involvement. Use this for vendor/partner integrations where consent is established out-of-band; use SubscribeToPage for subscriber-initiated signups that require double opt-in.\n operationId: StatusPageService_CreatePageSubscription\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreatePageSubscriptionRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreatePageSubscriptionResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/CreateStatusPage:\n post:\n tags:\n - StatusPageService\n summary: CreateStatusPage\n description: CreateStatusPage creates a new status page.\n operationId: StatusPageService_CreateStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.CreateStatusPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/DeleteComponentGroup:\n post:\n tags:\n - StatusPageService\n summary: DeleteComponentGroup\n description: DeleteComponentGroup removes a component group.\n operationId: StatusPageService_DeleteComponentGroup\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteComponentGroupRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteComponentGroupResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/DeleteStatusPage:\n post:\n tags:\n - StatusPageService\n summary: DeleteStatusPage\n description: DeleteStatusPage removes a status page.\n operationId: StatusPageService_DeleteStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.DeleteStatusPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetOverallStatus:\n get:\n tags:\n - StatusPageService\n summary: GetOverallStatus\n description: 'Returns the overall status of a status page along with individual component statuses. The overall status is computed from active status reports and maintenances with the following priority: degraded (from active status reports) > maintenance (from active maintenance windows) > operational.'\n operationId: StatusPageService_GetOverallStatus.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusResponse'\n post:\n tags:\n - StatusPageService\n summary: GetOverallStatus\n description: 'Returns the overall status of a status page along with individual component statuses. The overall status is computed from active status reports and maintenances with the following priority: degraded (from active status reports) > maintenance (from active maintenance windows) > operational.'\n operationId: StatusPageService_GetOverallStatus\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetOverallStatusResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponent:\n get:\n tags:\n - StatusPageService\n summary: GetPageComponent\n description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components.\n operationId: StatusPageService_GetPageComponent.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse'\n post:\n tags:\n - StatusPageService\n summary: GetPageComponent\n description: Returns a single status-page component by its ID, scoped to the authenticated workspace. Use this to resolve a component id to its name (and other fields) instead of fetching the whole page via GetStatusPageContent and filtering .components.\n operationId: StatusPageService_GetPageComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetPageComponentDailySummary:\n get:\n tags:\n - StatusPageService\n summary: GetPageComponentDailySummary\n description: 'Returns per-component daily status buckets (ok/degraded/error/count plus a resolved status) merged with the incident, maintenance, and status-report timeline over the last N days (max 45). Suitable as a single source of truth for status-bar and uptime-calendar rendering. Supports two access paths: by id (authenticated, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetPageComponentDailySummary.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryResponse'\n post:\n tags:\n - StatusPageService\n summary: GetPageComponentDailySummary\n description: 'Returns per-component daily status buckets (ok/degraded/error/count plus a resolved status) merged with the incident, maintenance, and status-report timeline over the last N days (max 45). Suitable as a single source of truth for status-bar and uptime-calendar rendering. Supports two access paths: by id (authenticated, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetPageComponentDailySummary\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetPageComponentDailySummaryResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetStatusPage:\n get:\n tags:\n - StatusPageService\n summary: GetStatusPage\n description: GetStatusPage retrieves a specific status page by ID.\n operationId: StatusPageService_GetStatusPage.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageResponse'\n post:\n tags:\n - StatusPageService\n summary: GetStatusPage\n description: GetStatusPage retrieves a specific status page by ID.\n operationId: StatusPageService_GetStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetStatusPageContent:\n get:\n tags:\n - StatusPageService\n summary: GetStatusPageContent\n description: 'Returns the full content of a status page including its components, component groups, active status reports, and scheduled maintenances. Supports two access paths: by ID (requires authentication, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetStatusPageContent.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentResponse'\n post:\n tags:\n - StatusPageService\n summary: GetStatusPageContent\n description: 'Returns the full content of a status page including its components, component groups, active status reports, and scheduled maintenances. Supports two access paths: by ID (requires authentication, workspace-scoped) or by slug (public access, requires the page to be published with access_type=PUBLIC).'\n operationId: StatusPageService_GetStatusPageContent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageContentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/GetStatusPageOverview:\n get:\n tags:\n - StatusPageService\n summary: GetStatusPageOverview\n description: 'Returns everything about a single status page in one authenticated call: the page, its rich rendering configuration, components, component groups, active/recent status reports, maintenances, and the computed overall + per-component statuses. Workspace-scoped by id; there is no public slug access path. Does not include uptime time-series — use GetPageComponentDailySummary for that.'\n operationId: StatusPageService_GetStatusPageOverview.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewResponse'\n post:\n tags:\n - StatusPageService\n summary: GetStatusPageOverview\n description: 'Returns everything about a single status page in one authenticated call: the page, its rich rendering configuration, components, component groups, active/recent status reports, maintenances, and the computed overall + per-component statuses. Workspace-scoped by id; there is no public slug access path. Does not include uptime time-series — use GetPageComponentDailySummary for that.'\n operationId: StatusPageService_GetStatusPageOverview\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.GetStatusPageOverviewResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/ListStatusPages:\n get:\n tags:\n - StatusPageService\n summary: ListStatusPages\n description: ListStatusPages returns all status pages for the workspace.\n operationId: StatusPageService_ListStatusPages.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesResponse'\n post:\n tags:\n - StatusPageService\n summary: ListStatusPages\n description: ListStatusPages returns all status pages for the workspace.\n operationId: StatusPageService_ListStatusPages\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListStatusPagesResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/ListSubscribers:\n get:\n tags:\n - StatusPageService\n summary: ListSubscribers\n description: ListSubscribers returns all subscribers for a status page.\n operationId: StatusPageService_ListSubscribers.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersResponse'\n post:\n tags:\n - StatusPageService\n summary: ListSubscribers\n description: ListSubscribers returns all subscribers for a status page.\n operationId: StatusPageService_ListSubscribers\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.ListSubscribersResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/RemoveComponent:\n post:\n tags:\n - StatusPageService\n summary: RemoveComponent\n description: RemoveComponent removes a component from a status page.\n operationId: StatusPageService_RemoveComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.RemoveComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.RemoveComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/SubscribeToPage:\n post:\n tags:\n - StatusPageService\n summary: 'SubscribeToPage: end-user email self-signup with double opt-in verification.'\n description: End-user email self-signup with double opt-in verification. A verification email is sent and the subscription activates only after the recipient confirms. If the email was previously unsubscribed, the existing row is reactivated instead of a duplicate being created. Use this for subscriber-initiated signups on the public status page; use CreatePageSubscription for operator-initiated (vendor) subscriptions that should skip verification.\n operationId: StatusPageService_SubscribeToPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.SubscribeToPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.SubscribeToPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UnsubscribeFromPage:\n post:\n tags:\n - StatusPageService\n summary: UnsubscribeFromPage\n description: UnsubscribeFromPage removes a subscription from a status page.\n operationId: StatusPageService_UnsubscribeFromPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UnsubscribeFromPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UnsubscribeFromPageResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UpdateComponent:\n post:\n tags:\n - StatusPageService\n summary: UpdateComponent\n description: UpdateComponent updates an existing component.\n operationId: StatusPageService_UpdateComponent\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UpdateComponentGroup:\n post:\n tags:\n - StatusPageService\n summary: UpdateComponentGroup\n description: UpdateComponentGroup updates an existing component group.\n operationId: StatusPageService_UpdateComponentGroup\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentGroupRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateComponentGroupResponse'\n /rpc/openstatus.status_page.v1.StatusPageService/UpdateStatusPage:\n post:\n tags:\n - StatusPageService\n summary: UpdateStatusPage\n description: UpdateStatusPage updates an existing status page.\n operationId: StatusPageService_UpdateStatusPage\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateStatusPageRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_page.v1.UpdateStatusPageResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/AddStatusReportUpdate:\n post:\n tags:\n - StatusReportService\n summary: AddStatusReportUpdate\n description: 'Adds a new update entry to an existing status report and transitions the report to the specified status. Status reports follow a lifecycle: investigating -> identified -> monitoring -> resolved. If notify is true, subscribers of the associated page are notified by email about the update.'\n operationId: StatusReportService_AddStatusReportUpdate\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.AddStatusReportUpdateRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.AddStatusReportUpdateResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/CreateStatusReport:\n post:\n tags:\n - StatusReportService\n summary: CreateStatusReport\n description: Creates a new status report with an initial update entry. The report is associated with a status page and optionally specific page components. An initial StatusReportUpdate is created automatically with the provided status, message, and date. If notify is true, subscribers of the associated page are notified by email.\n operationId: StatusReportService_CreateStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.CreateStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.CreateStatusReportResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/DeleteStatusReport:\n post:\n tags:\n - StatusReportService\n summary: DeleteStatusReport\n description: DeleteStatusReport removes a status report and all its updates.\n operationId: StatusReportService_DeleteStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.DeleteStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.DeleteStatusReportResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/GetStatusReport:\n get:\n tags:\n - StatusReportService\n summary: GetStatusReport\n description: GetStatusReport retrieves a specific status report by ID (includes full update timeline).\n operationId: StatusReportService_GetStatusReport.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportResponse'\n post:\n tags:\n - StatusReportService\n summary: GetStatusReport\n description: GetStatusReport retrieves a specific status report by ID (includes full update timeline).\n operationId: StatusReportService_GetStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.GetStatusReportResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/ListStatusReports:\n get:\n tags:\n - StatusReportService\n summary: ListStatusReports\n description: ListStatusReports returns all status reports for the workspace (metadata only).\n operationId: StatusReportService_ListStatusReports.get\n parameters:\n - name: message\n in: query\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsRequest'\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsResponse'\n post:\n tags:\n - StatusReportService\n summary: ListStatusReports\n description: ListStatusReports returns all status reports for the workspace (metadata only).\n operationId: StatusReportService_ListStatusReports\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.ListStatusReportsResponse'\n /rpc/openstatus.status_report.v1.StatusReportService/UpdateStatusReport:\n post:\n tags:\n - StatusReportService\n summary: UpdateStatusReport\n description: UpdateStatusReport updates the metadata of a status report (title, page components).\n operationId: StatusReportService_UpdateStatusReport\n requestBody:\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.UpdateStatusReportRequest'\n required: true\n responses:\n \"429\":\n $ref: '#/components/responses/RateLimited'\n default:\n description: Error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/connect.error'\n \"200\":\n description: Success\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/openstatus.status_report.v1.UpdateStatusReportResponse'\n"; diff --git a/apps/server/static/openapi.json b/apps/server/static/openapi.json index 6ecbc28a..04b2eec5 100644 --- a/apps/server/static/openapi.json +++ b/apps/server/static/openapi.json @@ -158,89 +158,192 @@ ], "description": "ServingStatus represents the health status of the service." }, - "openstatus.maintenance.v1.CreateMaintenanceRequest": { + "openstatus.incident.v1.AddIncidentNoteRequest": { "type": "object", "properties": { - "title": { + "id": { "type": "string", - "examples": ["Database Migration"], - "title": "title", - "maxLength": 256, + "title": "id", "minLength": 1, - "description": "Title of the maintenance (required, 1-256 characters)." + "description": "ID of the incident (required)." }, "message": { "type": "string", "title": "message", + "maxLength": 10000, "minLength": 1, - "description": "Message describing the maintenance (required)." - }, - "from": { + "description": "Note text, markdown (required, 1-10000 characters)." + } + }, + "title": "AddIncidentNoteRequest", + "additionalProperties": false, + "description": "AddIncidentNoteRequest is the request to add a note to an incident timeline." + }, + "openstatus.incident.v1.AddIncidentNoteResponse": { + "type": "object", + "properties": { + "event": { + "title": "event", + "description": "The timeline event that was added.", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentEvent" + } + }, + "title": "AddIncidentNoteResponse", + "additionalProperties": false, + "description": "AddIncidentNoteResponse is the response after adding a note." + }, + "openstatus.incident.v1.ApprovePostmortemRequest": { + "type": "object", + "properties": { + "incidentId": { "type": "string", - "examples": ["2024-03-01T02:00:00Z"], - "title": "from", - "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", - "description": "Start time of the maintenance window (RFC 3339 format, required)." + "title": "incident_id", + "minLength": 1, + "description": "ID of the incident (required)." }, - "to": { + "close": { + "type": ["boolean", "null"], + "title": "close", + "description": "Also close the incident (optional, defaults to false)." + } + }, + "title": "ApprovePostmortemRequest", + "additionalProperties": false, + "description": "ApprovePostmortemRequest is the request to approve the postmortem of an incident." + }, + "openstatus.incident.v1.ApprovePostmortemResponse": { + "type": "object", + "properties": { + "postmortem": { + "title": "postmortem", + "description": "The approved postmortem.", + "$ref": "#/components/schemas/openstatus.incident.v1.Postmortem" + }, + "incident": { + "title": "incident", + "description": "The incident after approval (without its timeline).", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" + } + }, + "title": "ApprovePostmortemResponse", + "additionalProperties": false, + "description": "ApprovePostmortemResponse is the response after approving the postmortem." + }, + "openstatus.incident.v1.CloseIncidentRequest": { + "type": "object", + "properties": { + "id": { "type": "string", - "examples": ["2024-03-01T06:00:00Z"], - "title": "to", - "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", - "description": "End time of the maintenance window (RFC 3339 format, required)." + "title": "id", + "minLength": 1, + "description": "ID of the incident (required)." }, - "pageId": { + "skipPostmortem": { + "type": ["boolean", "null"], + "title": "skip_postmortem", + "description": "Close without an approved postmortem (optional, defaults to false)." + } + }, + "title": "CloseIncidentRequest", + "additionalProperties": false, + "description": "CloseIncidentRequest is the request to close a resolved incident." + }, + "openstatus.incident.v1.CloseIncidentResponse": { + "type": "object", + "properties": { + "incident": { + "title": "incident", + "description": "The closed incident (without its timeline).", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" + } + }, + "title": "CloseIncidentResponse", + "additionalProperties": false, + "description": "CloseIncidentResponse is the response after closing an incident." + }, + "openstatus.incident.v1.DeclareIncidentRequest": { + "type": "object", + "properties": { + "title": { "type": "string", - "title": "page_id", + "examples": ["Checkout API returns 502"], + "title": "title", + "maxLength": 256, "minLength": 1, - "description": "Page ID to associate with this maintenance (required)." + "description": "Title of the incident (required, 1-256 characters)." }, - "pageComponentIds": { - "type": "array", - "items": { - "type": "string" + "severity": { + "not": { + "enum": ["INCIDENT_SEVERITY_UNSPECIFIED"] }, - "title": "page_component_ids", - "description": "Page component IDs to associate with this maintenance (optional)." + "title": "severity", + "description": "Severity of the incident (required).", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentSeverity" }, - "notify": { + "summary": { + "type": ["string", "null"], + "title": "summary", + "maxLength": 4000, + "description": "Short human summary (optional, up to 4000 characters)." + }, + "commanderEmail": { + "type": ["string", "null"], + "examples": ["jane@example.com"], + "title": "commander_email", + "format": "email", + "description": "Email of the member who leads the response (optional, defaults to unassigned)." + }, + "startedAt": { + "type": ["string", "null"], + "examples": ["2024-03-15T10:30:00Z"], + "title": "started_at", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", + "description": "When the impact began (RFC 3339 format, optional, defaults to now)." + }, + "statusReportId": { + "type": ["string", "null"], + "title": "status_report_id", + "minLength": 1, + "description": "ID of a status report to link (optional)." + }, + "openSlackChannel": { "type": ["boolean", "null"], - "title": "notify", - "description": "Whether to notify subscribers about this maintenance (optional, defaults to false)." + "title": "open_slack_channel", + "description": "Whether to open a Slack channel for the incident when Slack is connected (optional, defaults to false)." } }, - "title": "CreateMaintenanceRequest", + "title": "DeclareIncidentRequest", "additionalProperties": false, - "description": "CreateMaintenanceRequest is the request to create a new maintenance window." + "description": "DeclareIncidentRequest is the request to declare a new incident." }, - "openstatus.maintenance.v1.CreateMaintenanceResponse": { + "openstatus.incident.v1.DeclareIncidentResponse": { "type": "object", "properties": { - "maintenance": { - "title": "maintenance", - "description": "The created maintenance.", - "$ref": "#/components/schemas/openstatus.maintenance.v1.Maintenance" + "incident": { + "title": "incident", + "description": "The declared incident.", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" } }, - "title": "CreateMaintenanceResponse", + "title": "DeclareIncidentResponse", "additionalProperties": false, - "description": "CreateMaintenanceResponse is the response after creating a maintenance window." + "description": "DeclareIncidentResponse is the response after declaring an incident." }, - "openstatus.maintenance.v1.DeleteMaintenanceRequest": { + "openstatus.incident.v1.DeleteIncidentRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", "minLength": 1, - "description": "ID of the maintenance to delete (required)." + "description": "ID of the incident (required)." } }, - "title": "DeleteMaintenanceRequest", + "title": "DeleteIncidentRequest", "additionalProperties": false, - "description": "DeleteMaintenanceRequest is the request to delete a maintenance window." + "description": "DeleteIncidentRequest is the request to delete an incident." }, - "openstatus.maintenance.v1.DeleteMaintenanceResponse": { + "openstatus.incident.v1.DeleteIncidentResponse": { "type": "object", "properties": { "success": { @@ -249,1043 +352,1348 @@ "description": "Whether the deletion was successful." } }, - "title": "DeleteMaintenanceResponse", + "title": "DeleteIncidentResponse", "additionalProperties": false, - "description": "DeleteMaintenanceResponse is the response after deleting a maintenance window." + "description": "DeleteIncidentResponse is the response after deleting an incident." }, - "openstatus.maintenance.v1.GetMaintenanceRequest": { + "openstatus.incident.v1.GetIncidentRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", "minLength": 1, - "description": "ID of the maintenance to retrieve (required)." + "description": "ID of the incident to retrieve (required)." } }, - "title": "GetMaintenanceRequest", + "title": "GetIncidentRequest", "additionalProperties": false, - "description": "GetMaintenanceRequest is the request to get a maintenance window by ID." + "description": "GetIncidentRequest is the request to get an incident by ID." }, - "openstatus.maintenance.v1.GetMaintenanceResponse": { + "openstatus.incident.v1.GetIncidentResponse": { "type": "object", "properties": { - "maintenance": { - "title": "maintenance", - "description": "The requested maintenance.", - "$ref": "#/components/schemas/openstatus.maintenance.v1.Maintenance" + "incident": { + "title": "incident", + "description": "The requested incident.", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" } }, - "title": "GetMaintenanceResponse", + "title": "GetIncidentResponse", "additionalProperties": false, - "description": "GetMaintenanceResponse is the response containing the maintenance window." + "description": "GetIncidentResponse is the response containing the incident and its timeline." }, - "openstatus.maintenance.v1.ListMaintenancesRequest": { + "openstatus.incident.v1.GetPostmortemRequest": { "type": "object", "properties": { - "limit": { - "type": ["integer", "null"], - "title": "limit", - "maximum": 100, - "minimum": 1, - "format": "int32", - "description": "Maximum number of maintenances to return (1-100, defaults to 50)." - }, - "offset": { - "type": ["integer", "null"], - "title": "offset", - "minimum": 0, - "format": "int32", - "description": "Number of maintenances to skip for pagination (defaults to 0)." - }, - "pageId": { - "type": ["string", "null"], - "title": "page_id", - "description": "Filter by page ID (optional)." + "incidentId": { + "type": "string", + "title": "incident_id", + "minLength": 1, + "description": "ID of the incident (required)." } }, - "title": "ListMaintenancesRequest", + "title": "GetPostmortemRequest", "additionalProperties": false, - "description": "ListMaintenancesRequest is the request to list maintenance windows." + "description": "GetPostmortemRequest is the request to get the postmortem of an incident." }, - "openstatus.maintenance.v1.ListMaintenancesResponse": { + "openstatus.incident.v1.GetPostmortemResponse": { "type": "object", "properties": { - "maintenances": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary" - }, - "title": "maintenances", - "description": "List of maintenances." - }, - "totalSize": { - "type": "integer", - "title": "total_size", - "format": "int32", - "description": "Total number of maintenances matching the filter." + "postmortem": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.Postmortem" + }, + { + "type": "null" + } + ], + "title": "postmortem", + "description": "The postmortem (unset when none has been written yet)." } }, - "title": "ListMaintenancesResponse", + "title": "GetPostmortemResponse", "additionalProperties": false, - "description": "ListMaintenancesResponse is the response containing maintenance window summaries." + "description": "GetPostmortemResponse is the response containing the postmortem, if any." }, - "openstatus.maintenance.v1.Maintenance": { + "openstatus.incident.v1.Incident": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "description": "Unique identifier for the maintenance." + "description": "Unique identifier for the incident." }, "title": { "type": "string", "title": "title", - "description": "Title of the maintenance." + "description": "Title of the incident." }, - "message": { - "type": "string", - "title": "message", - "description": "Message describing the maintenance." + "severity": { + "title": "severity", + "description": "Severity of the incident.", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentSeverity" }, - "from": { - "type": "string", - "title": "from", - "description": "Start time of the maintenance window (RFC 3339 format)." + "status": { + "title": "status", + "description": "Current status of the incident.", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentStatus" }, - "to": { + "summary": { + "type": ["string", "null"], + "title": "summary", + "description": "Short human summary." + }, + "commander": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentUser" + }, + { + "type": "null" + } + ], + "title": "commander", + "description": "Member leading the response (unset when unassigned)." + }, + "declaredBy": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentUser" + }, + { + "type": "null" + } + ], + "title": "declared_by", + "description": "Member who declared the incident (unset for API keys without a creator)." + }, + "resolvedBy": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentUser" + }, + { + "type": "null" + } + ], + "title": "resolved_by", + "description": "Member who last resolved the incident." + }, + "declaredAt": { "type": "string", - "title": "to", - "description": "End time of the maintenance window (RFC 3339 format)." + "title": "declared_at", + "description": "Timestamp when the incident was declared (RFC 3339 format)." }, - "pageId": { + "startedAt": { "type": "string", - "title": "page_id", - "description": "ID of the page this maintenance is associated with." + "title": "started_at", + "description": "Timestamp when the impact began (RFC 3339 format)." }, - "pageComponentIds": { + "mitigatedAt": { + "type": ["string", "null"], + "title": "mitigated_at", + "description": "Timestamp when the incident was first mitigated (RFC 3339 format)." + }, + "resolvedAt": { + "type": ["string", "null"], + "title": "resolved_at", + "description": "Timestamp of the last resolution (RFC 3339 format)." + }, + "closedAt": { + "type": ["string", "null"], + "title": "closed_at", + "description": "Timestamp when the incident was closed or canceled (RFC 3339 format).\n A closed incident is read-only except for its postmortem." + }, + "statusReport": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentStatusReport" + }, + { + "type": "null" + } + ], + "title": "status_report", + "description": "Linked public status report." + }, + "slackChannelUrl": { + "type": ["string", "null"], + "title": "slack_channel_url", + "description": "Link to the bound Slack channel." + }, + "allowedTransitions": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentStatus" }, - "title": "page_component_ids", - "description": "IDs of affected page components." + "title": "allowed_transitions", + "description": "Statuses SetIncidentStatus accepts from the current state (empty once closed)." + }, + "deletable": { + "type": "boolean", + "title": "deletable", + "description": "Whether DeleteIncident is allowed: only while open and never mitigated, resolved or closed." + }, + "events": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentEvent" + }, + "title": "events", + "description": "Timeline, newest first (only included in GetIncident)." }, "createdAt": { "type": "string", "title": "created_at", - "description": "Timestamp when the maintenance was created (RFC 3339 format)." + "description": "Timestamp when the incident was created (RFC 3339 format)." }, "updatedAt": { "type": "string", "title": "updated_at", - "description": "Timestamp when the maintenance was last updated (RFC 3339 format)." + "description": "Timestamp when the incident was last updated (RFC 3339 format)." } }, - "title": "Maintenance", + "title": "Incident", "additionalProperties": false, - "description": "Maintenance represents a maintenance window with full details." + "description": "Incident is a managed incident with full details." }, - "openstatus.maintenance.v1.MaintenanceSummary": { + "openstatus.incident.v1.IncidentEvent": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "description": "Unique identifier for the maintenance." + "description": "Unique identifier for the event." }, - "title": { - "type": "string", - "title": "title", - "description": "Title of the maintenance." + "type": { + "title": "type", + "description": "Kind of event.", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentEventType" }, "message": { "type": "string", "title": "message", - "description": "Message describing the maintenance." + "description": "Text of the event: the note for notes, a rendered summary otherwise." }, - "from": { - "type": "string", - "title": "from", - "description": "Start time of the maintenance window (RFC 3339 format)." + "createdBy": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentUser" + }, + { + "type": "null" + } + ], + "title": "created_by", + "description": "Member who caused the event (unset for system events and API keys without a creator)." }, - "to": { + "createdAt": { "type": "string", - "title": "to", - "description": "End time of the maintenance window (RFC 3339 format)." - }, - "pageId": { + "title": "created_at", + "description": "Timestamp when the event was recorded (RFC 3339 format)." + } + }, + "title": "IncidentEvent", + "additionalProperties": false, + "description": "IncidentEvent is one entry of an incident timeline." + }, + "openstatus.incident.v1.IncidentEventType": { + "type": "string", + "title": "IncidentEventType", + "enum": [ + "INCIDENT_EVENT_TYPE_UNSPECIFIED", + "INCIDENT_EVENT_TYPE_DECLARED", + "INCIDENT_EVENT_TYPE_SEVERITY_CHANGED", + "INCIDENT_EVENT_TYPE_STATUS_CHANGED", + "INCIDENT_EVENT_TYPE_COMMANDER_CHANGED", + "INCIDENT_EVENT_TYPE_STARTED_AT_CHANGED", + "INCIDENT_EVENT_TYPE_NOTE", + "INCIDENT_EVENT_TYPE_STATUS_REPORT_LINKED", + "INCIDENT_EVENT_TYPE_STATUS_REPORT_UNLINKED", + "INCIDENT_EVENT_TYPE_SLACK_CHANNEL_BOUND", + "INCIDENT_EVENT_TYPE_SLACK_CHANNEL_UNBOUND", + "INCIDENT_EVENT_TYPE_RESOLVED", + "INCIDENT_EVENT_TYPE_CANCELED", + "INCIDENT_EVENT_TYPE_POSTMORTEM_DRAFTED", + "INCIDENT_EVENT_TYPE_POSTMORTEM_UPDATED", + "INCIDENT_EVENT_TYPE_POSTMORTEM_APPROVED", + "INCIDENT_EVENT_TYPE_CLOSED" + ], + "description": "IncidentEventType is the kind of entry in an incident timeline." + }, + "openstatus.incident.v1.IncidentSeverity": { + "type": "string", + "title": "IncidentSeverity", + "enum": [ + "INCIDENT_SEVERITY_UNSPECIFIED", + "INCIDENT_SEVERITY_CRITICAL", + "INCIDENT_SEVERITY_MAJOR", + "INCIDENT_SEVERITY_MINOR" + ], + "description": "IncidentSeverity is how bad an incident is." + }, + "openstatus.incident.v1.IncidentStatus": { + "type": "string", + "title": "IncidentStatus", + "enum": [ + "INCIDENT_STATUS_UNSPECIFIED", + "INCIDENT_STATUS_OPEN", + "INCIDENT_STATUS_MITIGATED", + "INCIDENT_STATUS_RESOLVED", + "INCIDENT_STATUS_CANCELED" + ], + "description": "IncidentStatus is the lifecycle state of an incident.\n Closed is not a status: a closed incident has closed_at set and keeps its last status." + }, + "openstatus.incident.v1.IncidentStatusReport": { + "type": "object", + "properties": { + "id": { "type": "string", - "title": "page_id", - "description": "ID of the page this maintenance is associated with." - }, - "pageComponentIds": { - "type": "array", - "items": { - "type": "string" - }, - "title": "page_component_ids", - "description": "IDs of affected page components." + "title": "id", + "description": "ID of the status report." }, - "createdAt": { + "title": { "type": "string", - "title": "created_at", - "description": "Timestamp when the maintenance was created (RFC 3339 format)." + "title": "title", + "description": "Title of the status report." }, - "updatedAt": { + "status": { + "title": "status", + "description": "Current status of the status report.", + "$ref": "#/components/schemas/openstatus.status_report.v1.StatusReportStatus" + }, + "pageId": { "type": "string", - "title": "updated_at", - "description": "Timestamp when the maintenance was last updated (RFC 3339 format)." + "title": "page_id", + "description": "ID of the status page the report belongs to." } }, - "title": "MaintenanceSummary", + "title": "IncidentStatusReport", "additionalProperties": false, - "description": "MaintenanceSummary represents metadata for a maintenance window (used in list responses)." + "description": "IncidentStatusReport is the public status report linked to an incident." }, - "openstatus.maintenance.v1.UpdateMaintenanceRequest": { + "openstatus.incident.v1.IncidentSummary": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "minLength": 1, - "description": "ID of the maintenance to update (required)." + "description": "Unique identifier for the incident." }, "title": { - "type": ["string", "null"], + "type": "string", "title": "title", - "maxLength": 256, - "minLength": 1, - "description": "New title for the maintenance (optional)." + "description": "Title of the incident." }, - "message": { - "type": ["string", "null"], - "title": "message", - "description": "New message for the maintenance (optional)." + "severity": { + "title": "severity", + "description": "Severity of the incident.", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentSeverity" }, - "from": { - "type": ["string", "null"], - "title": "from", - "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", - "description": "New start time (RFC 3339 format, optional)." + "status": { + "title": "status", + "description": "Current status of the incident.", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentStatus" }, - "to": { + "commander": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentUser" + }, + { + "type": "null" + } + ], + "title": "commander", + "description": "Member leading the response (unset when unassigned)." + }, + "declaredAt": { + "type": "string", + "title": "declared_at", + "description": "Timestamp when the incident was declared (RFC 3339 format)." + }, + "startedAt": { + "type": "string", + "title": "started_at", + "description": "Timestamp when the impact began (RFC 3339 format)." + }, + "resolvedAt": { "type": ["string", "null"], - "title": "to", - "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", - "description": "New end time (RFC 3339 format, optional)." + "title": "resolved_at", + "description": "Timestamp of the last resolution (RFC 3339 format)." }, - "pageId": { + "closedAt": { "type": ["string", "null"], - "title": "page_id", - "description": "Deprecated: page_id is now derived from page_component_ids.", - "deprecated": true + "title": "closed_at", + "description": "Timestamp when the incident was closed or canceled (RFC 3339 format)." }, - "pageComponentIds": { - "type": "array", - "items": { - "type": "string" - }, - "title": "page_component_ids", - "description": "New list of page component IDs (optional, replaces existing list)." + "statusReport": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentStatusReport" + }, + { + "type": "null" + } + ], + "title": "status_report", + "description": "Linked public status report." }, - "updatePageComponentIds": { - "type": ["boolean", "null"], - "title": "update_page_component_ids", - "description": "Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved." + "createdAt": { + "type": "string", + "title": "created_at", + "description": "Timestamp when the incident was created (RFC 3339 format)." + }, + "updatedAt": { + "type": "string", + "title": "updated_at", + "description": "Timestamp when the incident was last updated (RFC 3339 format)." } }, - "title": "UpdateMaintenanceRequest", + "title": "IncidentSummary", "additionalProperties": false, - "description": "UpdateMaintenanceRequest is the request to update a maintenance window." + "description": "IncidentSummary is the metadata of an incident (used in list responses)." }, - "openstatus.maintenance.v1.UpdateMaintenanceResponse": { + "openstatus.incident.v1.IncidentUser": { "type": "object", "properties": { - "maintenance": { - "title": "maintenance", - "description": "The updated maintenance.", - "$ref": "#/components/schemas/openstatus.maintenance.v1.Maintenance" + "email": { + "type": "string", + "title": "email", + "description": "Email address of the member (empty for a deleted account)." + }, + "name": { + "type": "string", + "title": "name", + "description": "Display name of the member (\"Deleted user\" for a deleted account)." } }, - "title": "UpdateMaintenanceResponse", + "title": "IncidentUser", "additionalProperties": false, - "description": "UpdateMaintenanceResponse is the response after updating a maintenance window." + "description": "IncidentUser is a workspace member referenced by an incident." }, - "openstatus.monitor.v1.BodyAssertion": { + "openstatus.incident.v1.LinkStatusReportRequest": { "type": "object", "properties": { - "target": { + "id": { "type": "string", - "title": "target", - "description": "Target value to compare against." + "title": "id", + "minLength": 1, + "description": "ID of the incident (required)." }, - "comparator": { - "not": { - "enum": ["STRING_COMPARATOR_UNSPECIFIED"] - }, - "title": "comparator", - "description": "Comparison operation (required, must not be UNSPECIFIED).", - "$ref": "#/components/schemas/openstatus.monitor.v1.StringComparator" + "statusReportId": { + "type": "string", + "title": "status_report_id", + "minLength": 1, + "description": "ID of the status report to link (required)." } }, - "title": "BodyAssertion", + "title": "LinkStatusReportRequest", "additionalProperties": false, - "description": "BodyAssertion defines an assertion for response body content." + "description": "LinkStatusReportRequest is the request to link a status report to an incident." }, - "openstatus.monitor.v1.CreateDNSMonitorRequest": { + "openstatus.incident.v1.LinkStatusReportResponse": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "Monitor configuration (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + "incident": { + "title": "incident", + "description": "The updated incident (without its timeline).", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" } }, - "title": "CreateDNSMonitorRequest", - "required": ["monitor"], + "title": "LinkStatusReportResponse", "additionalProperties": false, - "description": "CreateDNSMonitorRequest is the request to create a new DNS monitor." + "description": "LinkStatusReportResponse is the response after linking a status report." }, - "openstatus.monitor.v1.CreateDNSMonitorResponse": { + "openstatus.incident.v1.ListIncidentsRequest": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "The created monitor with assigned ID.", - "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + "limit": { + "type": ["integer", "null"], + "title": "limit", + "maximum": 100, + "minimum": 1, + "format": "int32", + "description": "Maximum number of incidents to return (1-100, defaults to 50)." + }, + "offset": { + "type": ["integer", "null"], + "title": "offset", + "minimum": 0, + "format": "int32", + "description": "Number of incidents to skip for pagination (defaults to 0)." + }, + "statuses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentStatus" + }, + "title": "statuses", + "description": "Filter by status (optional). If empty, returns all statuses." + }, + "closed": { + "type": ["boolean", "null"], + "title": "closed", + "description": "Filter by closed state (optional). If unset, returns both." } }, - "title": "CreateDNSMonitorResponse", + "title": "ListIncidentsRequest", "additionalProperties": false, - "description": "CreateDNSMonitorResponse is the response after creating a DNS monitor." + "description": "ListIncidentsRequest is the request to list incidents." }, - "openstatus.monitor.v1.CreateGRPCMonitorRequest": { + "openstatus.incident.v1.ListIncidentsResponse": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "Monitor configuration (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" + "incidents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentSummary" + }, + "title": "incidents", + "description": "List of incidents (metadata only, use GetIncident for full details)." + }, + "totalSize": { + "type": "integer", + "title": "total_size", + "format": "int32", + "description": "Total number of incidents matching the filter." } }, - "title": "CreateGRPCMonitorRequest", - "required": ["monitor"], + "title": "ListIncidentsResponse", "additionalProperties": false, - "description": "CreateGRPCMonitorRequest is the request to create a new gRPC monitor." + "description": "ListIncidentsResponse is the response containing incident summaries." }, - "openstatus.monitor.v1.CreateGRPCMonitorResponse": { + "openstatus.incident.v1.Postmortem": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "The created monitor with assigned ID.", - "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" + "incidentId": { + "type": "string", + "title": "incident_id", + "description": "ID of the incident the postmortem belongs to." + }, + "status": { + "title": "status", + "description": "Review state of the postmortem.", + "$ref": "#/components/schemas/openstatus.incident.v1.PostmortemStatus" + }, + "content": { + "type": "string", + "title": "content", + "description": "Markdown body of the postmortem." + }, + "draftedBy": { + "title": "drafted_by", + "description": "Who wrote the current body.", + "$ref": "#/components/schemas/openstatus.incident.v1.PostmortemAuthor" + }, + "approvedBy": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentUser" + }, + { + "type": "null" + } + ], + "title": "approved_by", + "description": "Member who approved the postmortem." + }, + "approvedAt": { + "type": ["string", "null"], + "title": "approved_at", + "description": "Timestamp when the postmortem was approved (RFC 3339 format)." + }, + "createdAt": { + "type": "string", + "title": "created_at", + "description": "Timestamp when the postmortem was created (RFC 3339 format)." + }, + "updatedAt": { + "type": "string", + "title": "updated_at", + "description": "Timestamp when the postmortem was last updated (RFC 3339 format)." } }, - "title": "CreateGRPCMonitorResponse", + "title": "Postmortem", "additionalProperties": false, - "description": "CreateGRPCMonitorResponse is the response after creating a gRPC monitor." + "description": "Postmortem is the review written after an incident is resolved." }, - "openstatus.monitor.v1.CreateHTTPMonitorRequest": { - "type": "object", - "properties": { - "monitor": { - "title": "monitor", - "description": "Monitor configuration (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" - } - }, - "title": "CreateHTTPMonitorRequest", - "required": ["monitor"], - "additionalProperties": false, - "description": "CreateHTTPMonitorRequest is the request to create a new HTTP monitor." + "openstatus.incident.v1.PostmortemAuthor": { + "type": "string", + "title": "PostmortemAuthor", + "enum": [ + "POSTMORTEM_AUTHOR_UNSPECIFIED", + "POSTMORTEM_AUTHOR_AGENT", + "POSTMORTEM_AUTHOR_USER" + ], + "description": "PostmortemAuthor is who wrote the current postmortem body." }, - "openstatus.monitor.v1.CreateHTTPMonitorResponse": { - "type": "object", - "properties": { - "monitor": { - "title": "monitor", - "description": "The created monitor with assigned ID.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" - } - }, - "title": "CreateHTTPMonitorResponse", - "additionalProperties": false, - "description": "CreateHTTPMonitorResponse is the response after creating an HTTP monitor." + "openstatus.incident.v1.PostmortemStatus": { + "type": "string", + "title": "PostmortemStatus", + "enum": [ + "POSTMORTEM_STATUS_UNSPECIFIED", + "POSTMORTEM_STATUS_DRAFT", + "POSTMORTEM_STATUS_APPROVED" + ], + "description": "PostmortemStatus is the review state of a postmortem." }, - "openstatus.monitor.v1.CreateICMPMonitorRequest": { + "openstatus.incident.v1.SetIncidentStatusRequest": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "Monitor configuration (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + "id": { + "type": "string", + "title": "id", + "minLength": 1, + "description": "ID of the incident (required)." + }, + "status": { + "not": { + "enum": ["INCIDENT_STATUS_UNSPECIFIED"] + }, + "title": "status", + "description": "Target status (required).", + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentStatus" + }, + "note": { + "type": ["string", "null"], + "title": "note", + "maxLength": 10000, + "description": "Note recorded with the change (optional, up to 10000 characters)." } }, - "title": "CreateICMPMonitorRequest", - "required": ["monitor"], + "title": "SetIncidentStatusRequest", "additionalProperties": false, - "description": "CreateICMPMonitorRequest is the request to create a new ICMP monitor." + "description": "SetIncidentStatusRequest is the request to change the status of an incident." }, - "openstatus.monitor.v1.CreateICMPMonitorResponse": { + "openstatus.incident.v1.SetIncidentStatusResponse": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "The created monitor with assigned ID.", - "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + "incident": { + "title": "incident", + "description": "The updated incident (without its timeline).", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" } }, - "title": "CreateICMPMonitorResponse", + "title": "SetIncidentStatusResponse", "additionalProperties": false, - "description": "CreateICMPMonitorResponse is the response after creating an ICMP monitor." + "description": "SetIncidentStatusResponse is the response after changing the status of an incident." }, - "openstatus.monitor.v1.CreateTCPMonitorRequest": { + "openstatus.incident.v1.UnlinkStatusReportRequest": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "Monitor configuration (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + "id": { + "type": "string", + "title": "id", + "minLength": 1, + "description": "ID of the incident (required)." } }, - "title": "CreateTCPMonitorRequest", - "required": ["monitor"], + "title": "UnlinkStatusReportRequest", "additionalProperties": false, - "description": "CreateTCPMonitorRequest is the request to create a new TCP monitor." + "description": "UnlinkStatusReportRequest is the request to unlink the status report of an incident." }, - "openstatus.monitor.v1.CreateTCPMonitorResponse": { + "openstatus.incident.v1.UnlinkStatusReportResponse": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "The created monitor with assigned ID.", - "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + "incident": { + "title": "incident", + "description": "The updated incident (without its timeline).", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" } }, - "title": "CreateTCPMonitorResponse", + "title": "UnlinkStatusReportResponse", "additionalProperties": false, - "description": "CreateTCPMonitorResponse is the response after creating a TCP monitor." + "description": "UnlinkStatusReportResponse is the response after unlinking the status report." }, - "openstatus.monitor.v1.DNSMonitor": { + "openstatus.incident.v1.UpdateIncidentRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "description": "Unique identifier for the monitor (output only for create requests)." - }, - "name": { - "type": "string", - "examples": ["DNS Resolution Check"], - "title": "name", - "maxLength": 256, "minLength": 1, - "description": "Name of the monitor (required, max 256 characters)." + "description": "ID of the incident to update (required)." }, - "uri": { - "type": "string", - "examples": ["example.com"], - "title": "uri", - "maxLength": 2048, + "title": { + "type": ["string", "null"], + "title": "title", + "maxLength": 256, "minLength": 1, - "description": "Domain to resolve (required, max 2048 characters)." + "description": "New title (optional, 1-256 characters)." }, - "periodicity": { + "severity": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.incident.v1.IncidentSeverity" + }, + { + "type": "null" + } + ], "not": { - "enum": ["PERIODICITY_UNSPECIFIED"] - }, - "title": "periodicity", - "description": "Check periodicity (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" - }, - "timeout": { - "type": ["integer", "string"], - "title": "timeout", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Timeout in milliseconds (0-120000, defaults to 45000)." - }, - "degradedAt": { - "type": ["integer", "string", "null"], - "title": "degraded_at", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." - }, - "retry": { - "type": ["integer", "string"], - "title": "retry", - "maximum": 10, - "minimum": 0, - "format": "int64", - "description": "Number of retry attempts (0-10, defaults to 3)." - }, - "recordAssertions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.RecordAssertion" + "enum": ["INCIDENT_SEVERITY_UNSPECIFIED"] }, - "title": "record_assertions", - "maxItems": 10, - "description": "DNS record assertions for validation." + "title": "severity", + "description": "New severity (optional)." }, - "description": { + "summary": { "type": ["string", "null"], - "title": "description", - "maxLength": 1024, - "description": "Description of the monitor (optional)." - }, - "active": { - "type": ["boolean", "null"], - "title": "active", - "description": "Whether the monitor is active (defaults to false)." + "title": "summary", + "maxLength": 4000, + "minLength": 1, + "description": "New summary (optional, 1-4000 characters)." }, - "public": { + "clearSummary": { "type": ["boolean", "null"], - "title": "public", - "description": "Whether the monitor is publicly visible (defaults to false)." - }, - "regions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.Region" - }, - "title": "regions", - "maxItems": 28, - "description": "Geographic regions to run checks from." + "title": "clear_summary", + "description": "Set to true to remove the summary. Cannot be combined with summary." }, - "openTelemetry": { - "title": "open_telemetry", - "description": "OpenTelemetry configuration for exporting metrics.", - "$ref": "#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig" + "commanderEmail": { + "type": ["string", "null"], + "title": "commander_email", + "format": "email", + "description": "Email of the new commander (optional)." }, - "status": { - "title": "status", - "description": "Current operational status of the monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" + "clearCommander": { + "type": ["boolean", "null"], + "title": "clear_commander", + "description": "Set to true to unassign the commander. Cannot be combined with commander_email." }, - "privateLocationIds": { - "type": "array", - "items": { - "type": "string", - "readOnly": true - }, - "title": "private_location_ids", - "description": "IDs of private locations that run this monitor. Read-only.", - "readOnly": true + "startedAt": { + "type": ["string", "null"], + "title": "started_at", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", + "description": "New start of impact (RFC 3339 format, optional)." } }, - "title": "DNSMonitor", + "title": "UpdateIncidentRequest", "additionalProperties": false, - "description": "DNSMonitor defines the configuration for a DNS monitor." + "description": "UpdateIncidentRequest is the request to edit an incident." }, - "openstatus.monitor.v1.DeleteMonitorRequest": { + "openstatus.incident.v1.UpdateIncidentResponse": { "type": "object", "properties": { - "id": { + "incident": { + "title": "incident", + "description": "The updated incident (without its timeline).", + "$ref": "#/components/schemas/openstatus.incident.v1.Incident" + } + }, + "title": "UpdateIncidentResponse", + "additionalProperties": false, + "description": "UpdateIncidentResponse is the response after updating an incident." + }, + "openstatus.incident.v1.UpdatePostmortemRequest": { + "type": "object", + "properties": { + "incidentId": { "type": "string", - "title": "id", + "title": "incident_id", "minLength": 1, - "description": "Monitor ID to delete (required)." + "description": "ID of the incident (required)." + }, + "content": { + "type": "string", + "title": "content", + "maxLength": 100000, + "minLength": 1, + "description": "Markdown body (required, 1-100000 characters). Replaces the current body; an approved postmortem stays approved." } }, - "title": "DeleteMonitorRequest", + "title": "UpdatePostmortemRequest", "additionalProperties": false, - "description": "DeleteMonitorRequest is the request to delete a monitor." + "description": "UpdatePostmortemRequest is the request to write the postmortem of a resolved incident." }, - "openstatus.monitor.v1.DeleteMonitorResponse": { + "openstatus.incident.v1.UpdatePostmortemResponse": { "type": "object", "properties": { - "success": { - "type": "boolean", - "title": "success", - "description": "Whether the deletion was successful." + "postmortem": { + "title": "postmortem", + "description": "The saved postmortem.", + "$ref": "#/components/schemas/openstatus.incident.v1.Postmortem" } }, - "title": "DeleteMonitorResponse", + "title": "UpdatePostmortemResponse", "additionalProperties": false, - "description": "DeleteMonitorResponse is the response after deleting a monitor." + "description": "UpdatePostmortemResponse is the response after writing the postmortem." }, - "openstatus.monitor.v1.GRPCMonitor": { + "openstatus.maintenance.v1.CreateMaintenanceRequest": { "type": "object", "properties": { - "id": { - "type": "string", - "title": "id", - "description": "Unique identifier for the monitor (output only for create requests)." - }, - "name": { + "title": { "type": "string", - "examples": ["Checkout gRPC"], - "title": "name", + "examples": ["Database Migration"], + "title": "title", "maxLength": 256, "minLength": 1, - "description": "Name of the monitor (required, max 256 characters)." + "description": "Title of the maintenance (required, 1-256 characters)." }, - "uri": { + "message": { "type": "string", - "examples": ["api.example.com:443"], - "title": "uri", - "maxLength": 2048, + "title": "message", "minLength": 1, - "pattern": "^(\\[[0-9a-fA-F:]+\\]|[^:/\\s]+):[0-9]{1,5}$", - "description": "Target in \"host:port\" form. IPv6 addresses must be bracketed." + "description": "Message describing the maintenance (required)." }, - "periodicity": { - "not": { - "enum": ["PERIODICITY_UNSPECIFIED"] - }, - "title": "periodicity", - "description": "Check periodicity (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" + "from": { + "type": "string", + "examples": ["2024-03-01T02:00:00Z"], + "title": "from", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", + "description": "Start time of the maintenance window (RFC 3339 format, required)." }, - "timeout": { - "type": ["integer", "string"], - "title": "timeout", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Timeout in milliseconds (0-120000, defaults to 45000)." + "to": { + "type": "string", + "examples": ["2024-03-01T06:00:00Z"], + "title": "to", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", + "description": "End time of the maintenance window (RFC 3339 format, required)." }, - "degradedAt": { - "type": ["integer", "string", "null"], - "title": "degraded_at", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." - }, - "retry": { - "type": ["integer", "string"], - "title": "retry", - "maximum": 10, - "minimum": 0, - "format": "int64", - "description": "Number of retry attempts (0-10, defaults to 3)." - }, - "description": { - "type": ["string", "null"], - "title": "description", - "maxLength": 1024, - "description": "Description of the monitor (optional)." - }, - "active": { - "type": ["boolean", "null"], - "title": "active", - "description": "Whether the monitor is active (defaults to false)." - }, - "public": { - "type": ["boolean", "null"], - "title": "public", - "description": "Whether the monitor is publicly visible (defaults to false)." - }, - "regions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.Region" - }, - "title": "regions", - "maxItems": 28, - "description": "Geographic regions to run checks from." - }, - "openTelemetry": { - "title": "open_telemetry", - "description": "OpenTelemetry configuration for exporting metrics.", - "$ref": "#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig" - }, - "status": { - "title": "status", - "description": "Current operational status of the monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" - }, - "privateLocationIds": { - "type": "array", - "items": { - "type": "string", - "readOnly": true - }, - "title": "private_location_ids", - "description": "IDs of private locations that run this monitor. Read-only.", - "readOnly": true - }, - "service": { - "type": ["string", "null"], - "examples": ["checkout.v1.CheckoutService"], - "title": "service", - "maxLength": 512, - "description": "Service name passed to Health/Check. Empty means overall server health." - }, - "tlsMode": { - "oneOf": [ - { - "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCTlsMode" - }, - { - "type": "null" - } - ], - "title": "tls_mode", - "description": "How the connection to the target is secured. Defaults to TLS." + "pageId": { + "type": "string", + "title": "page_id", + "minLength": 1, + "description": "Page ID to associate with this maintenance (required)." }, - "metadata": { + "pageComponentIds": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.Headers" + "type": "string" }, - "title": "metadata", - "maxItems": 20, - "description": "Metadata sent with the health check request." - } - }, - "title": "GRPCMonitor", - "additionalProperties": false, - "description": "GRPCMonitor defines the configuration for a gRPC health check monitor.\n The probe calls grpc.health.v1.Health/Check on the target." - }, - "openstatus.monitor.v1.GRPCTlsMode": { - "type": "string", - "title": "GRPCTlsMode", - "enum": [ - "GRPC_TLS_MODE_UNSPECIFIED", - "GRPC_TLS_MODE_TLS", - "GRPC_TLS_MODE_PLAINTEXT", - "GRPC_TLS_MODE_TLS_INSECURE" - ], - "description": "GRPCTlsMode selects how the probe secures its connection to the target." - }, - "openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest": { - "type": "object", - "properties": { - "id": { - "type": "string", - "title": "id", - "minLength": 1, - "description": "Monitor ID to get a response log for (required)." + "title": "page_component_ids", + "description": "Page component IDs to associate with this maintenance (optional)." }, - "logId": { - "type": "string", - "title": "log_id", - "minLength": 1, - "description": "Response log ID to retrieve (required)." + "notify": { + "type": ["boolean", "null"], + "title": "notify", + "description": "Whether to notify subscribers about this maintenance (optional, defaults to false)." } }, - "title": "GetMonitorHTTPResponseLogRequest", + "title": "CreateMaintenanceRequest", "additionalProperties": false, - "description": "GetMonitorHTTPResponseLogRequest is the request to get one response log." + "description": "CreateMaintenanceRequest is the request to create a new maintenance window." }, - "openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse": { + "openstatus.maintenance.v1.CreateMaintenanceResponse": { "type": "object", "properties": { - "log": { - "title": "log", - "description": "Response log details.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogDetail" + "maintenance": { + "title": "maintenance", + "description": "The created maintenance.", + "$ref": "#/components/schemas/openstatus.maintenance.v1.Maintenance" } }, - "title": "GetMonitorHTTPResponseLogResponse", + "title": "CreateMaintenanceResponse", "additionalProperties": false, - "description": "GetMonitorHTTPResponseLogResponse is the response containing one response log." + "description": "CreateMaintenanceResponse is the response after creating a maintenance window." }, - "openstatus.monitor.v1.GetMonitorRequest": { + "openstatus.maintenance.v1.DeleteMaintenanceRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", "minLength": 1, - "description": "Monitor ID to retrieve (required)." + "description": "ID of the maintenance to delete (required)." } }, - "title": "GetMonitorRequest", + "title": "DeleteMaintenanceRequest", "additionalProperties": false, - "description": "GetMonitorRequest is the request to get a single monitor by ID." + "description": "DeleteMaintenanceRequest is the request to delete a maintenance window." }, - "openstatus.monitor.v1.GetMonitorResponse": { + "openstatus.maintenance.v1.DeleteMaintenanceResponse": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "The monitor configuration (one of HTTP, TCP, DNS, ICMP, or gRPC).", - "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorConfig" + "success": { + "type": "boolean", + "title": "success", + "description": "Whether the deletion was successful." } }, - "title": "GetMonitorResponse", + "title": "DeleteMaintenanceResponse", "additionalProperties": false, - "description": "GetMonitorResponse is the response containing the monitor." + "description": "DeleteMaintenanceResponse is the response after deleting a maintenance window." }, - "openstatus.monitor.v1.GetMonitorStatusRequest": { + "openstatus.maintenance.v1.GetMaintenanceRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", "minLength": 1, - "description": "Monitor ID to get status for (required)." + "description": "ID of the maintenance to retrieve (required)." } }, - "title": "GetMonitorStatusRequest", + "title": "GetMaintenanceRequest", "additionalProperties": false, - "description": "GetMonitorStatusRequest is the request to get the status of all regions for a monitor." + "description": "GetMaintenanceRequest is the request to get a maintenance window by ID." }, - "openstatus.monitor.v1.GetMonitorStatusResponse": { + "openstatus.maintenance.v1.GetMaintenanceResponse": { "type": "object", "properties": { - "id": { - "type": "string", - "title": "id", - "description": "Monitor ID." - }, - "regions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.RegionStatus" - }, - "title": "regions", - "description": "Status for each region." + "maintenance": { + "title": "maintenance", + "description": "The requested maintenance.", + "$ref": "#/components/schemas/openstatus.maintenance.v1.Maintenance" } }, - "title": "GetMonitorStatusResponse", + "title": "GetMaintenanceResponse", "additionalProperties": false, - "description": "GetMonitorStatusResponse is the response containing the status of all regions for a monitor." + "description": "GetMaintenanceResponse is the response containing the maintenance window." }, - "openstatus.monitor.v1.GetMonitorSummaryRequest": { + "openstatus.maintenance.v1.ListMaintenancesRequest": { "type": "object", "properties": { - "id": { - "type": "string", - "title": "id", - "minLength": 1, - "description": "Monitor ID to get summary for (required)." + "limit": { + "type": ["integer", "null"], + "title": "limit", + "maximum": 100, + "minimum": 1, + "format": "int32", + "description": "Maximum number of maintenances to return (1-100, defaults to 50)." }, - "timeRange": { - "title": "time_range", - "description": "Time range for metrics aggregation (defaults to 1 day if unspecified).", - "$ref": "#/components/schemas/openstatus.monitor.v1.TimeRange" + "offset": { + "type": ["integer", "null"], + "title": "offset", + "minimum": 0, + "format": "int32", + "description": "Number of maintenances to skip for pagination (defaults to 0)." }, - "regions": { + "pageId": { + "type": ["string", "null"], + "title": "page_id", + "description": "Filter by page ID (optional)." + } + }, + "title": "ListMaintenancesRequest", + "additionalProperties": false, + "description": "ListMaintenancesRequest is the request to list maintenance windows." + }, + "openstatus.maintenance.v1.ListMaintenancesResponse": { + "type": "object", + "properties": { + "maintenances": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.Region" + "$ref": "#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary" }, - "title": "regions", - "maxItems": 28, - "description": "Optional filter by regions. If empty, returns metrics for all regions." + "title": "maintenances", + "description": "List of maintenances." + }, + "totalSize": { + "type": "integer", + "title": "total_size", + "format": "int32", + "description": "Total number of maintenances matching the filter." } }, - "title": "GetMonitorSummaryRequest", + "title": "ListMaintenancesResponse", "additionalProperties": false, - "description": "GetMonitorSummaryRequest is the request to get aggregated metrics for a monitor." + "description": "ListMaintenancesResponse is the response containing maintenance window summaries." }, - "openstatus.monitor.v1.GetMonitorSummaryResponse": { + "openstatus.maintenance.v1.Maintenance": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "description": "Monitor ID." + "description": "Unique identifier for the maintenance." }, - "lastPingAt": { + "title": { "type": "string", - "title": "last_ping_at", - "description": "Timestamp of the last check in RFC 3339 format." - }, - "totalSuccessful": { - "type": ["integer", "string"], - "title": "total_successful", - "format": "int64", - "description": "Total number of successful requests." - }, - "totalDegraded": { - "type": ["integer", "string"], - "title": "total_degraded", - "format": "int64", - "description": "Total number of degraded requests." - }, - "totalFailed": { - "type": ["integer", "string"], - "title": "total_failed", - "format": "int64", - "description": "Total number of failed requests." - }, - "p50": { - "type": ["integer", "string"], - "title": "p50", - "format": "int64", - "description": "50th percentile (median) latency in milliseconds." - }, - "p75": { - "type": ["integer", "string"], - "title": "p75", - "format": "int64", - "description": "75th percentile latency in milliseconds." + "title": "title", + "description": "Title of the maintenance." }, - "p90": { - "type": ["integer", "string"], - "title": "p90", - "format": "int64", - "description": "90th percentile latency in milliseconds." + "message": { + "type": "string", + "title": "message", + "description": "Message describing the maintenance." }, - "p95": { - "type": ["integer", "string"], - "title": "p95", - "format": "int64", - "description": "95th percentile latency in milliseconds." + "from": { + "type": "string", + "title": "from", + "description": "Start time of the maintenance window (RFC 3339 format)." }, - "p99": { - "type": ["integer", "string"], - "title": "p99", - "format": "int64", - "description": "99th percentile latency in milliseconds." + "to": { + "type": "string", + "title": "to", + "description": "End time of the maintenance window (RFC 3339 format)." }, - "timeRange": { - "title": "time_range", - "description": "Time range used for the metrics.", - "$ref": "#/components/schemas/openstatus.monitor.v1.TimeRange" + "pageId": { + "type": "string", + "title": "page_id", + "description": "ID of the page this maintenance is associated with." }, - "regions": { + "pageComponentIds": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.Region" + "type": "string" }, - "title": "regions", - "description": "Regions included in the metrics." + "title": "page_component_ids", + "description": "IDs of affected page components." + }, + "createdAt": { + "type": "string", + "title": "created_at", + "description": "Timestamp when the maintenance was created (RFC 3339 format)." + }, + "updatedAt": { + "type": "string", + "title": "updated_at", + "description": "Timestamp when the maintenance was last updated (RFC 3339 format)." } }, - "title": "GetMonitorSummaryResponse", + "title": "Maintenance", "additionalProperties": false, - "description": "GetMonitorSummaryResponse is the response containing aggregated metrics for a monitor." - }, - "openstatus.monitor.v1.HTTPMethod": { - "type": "string", - "title": "HTTPMethod", - "enum": [ - "HTTP_METHOD_UNSPECIFIED", - "HTTP_METHOD_GET", - "HTTP_METHOD_POST", - "HTTP_METHOD_HEAD", - "HTTP_METHOD_PUT", - "HTTP_METHOD_PATCH", - "HTTP_METHOD_DELETE", - "HTTP_METHOD_TRACE", - "HTTP_METHOD_CONNECT", - "HTTP_METHOD_OPTIONS" - ], - "description": "HTTP methods supported for monitors." + "description": "Maintenance represents a maintenance window with full details." }, - "openstatus.monitor.v1.HTTPMonitor": { + "openstatus.maintenance.v1.MaintenanceSummary": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "description": "Unique identifier for the monitor (output only for create requests)." + "description": "Unique identifier for the maintenance." }, - "name": { + "title": { "type": "string", - "examples": ["Production API Health Check"], - "title": "name", - "maxLength": 256, - "minLength": 1, - "description": "Name of the monitor (required, max 256 characters)." + "title": "title", + "description": "Title of the maintenance." }, - "url": { + "message": { "type": "string", - "examples": ["https://api.example.com/health"], - "title": "url", - "maxLength": 2048, - "minLength": 1, - "format": "uri", - "description": "URL to monitor (required, max 2048 characters)." + "title": "message", + "description": "Message describing the maintenance." }, - "periodicity": { - "not": { - "enum": ["PERIODICITY_UNSPECIFIED"] - }, - "title": "periodicity", - "description": "Check periodicity (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" + "from": { + "type": "string", + "title": "from", + "description": "Start time of the maintenance window (RFC 3339 format)." }, - "method": { - "not": { - "enum": ["HTTP_METHOD_UNSPECIFIED"] + "to": { + "type": "string", + "title": "to", + "description": "End time of the maintenance window (RFC 3339 format)." + }, + "pageId": { + "type": "string", + "title": "page_id", + "description": "ID of the page this maintenance is associated with." + }, + "pageComponentIds": { + "type": "array", + "items": { + "type": "string" }, - "title": "method", - "description": "HTTP method to use (defaults to GET).", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMethod" + "title": "page_component_ids", + "description": "IDs of affected page components." }, - "body": { + "createdAt": { "type": "string", - "examples": ["map[key:value]"], - "title": "body", - "description": "Request body (optional)." + "title": "created_at", + "description": "Timestamp when the maintenance was created (RFC 3339 format)." }, - "timeout": { - "type": ["integer", "string"], - "title": "timeout", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Timeout in milliseconds (0-120000, defaults to 45000)." + "updatedAt": { + "type": "string", + "title": "updated_at", + "description": "Timestamp when the maintenance was last updated (RFC 3339 format)." + } + }, + "title": "MaintenanceSummary", + "additionalProperties": false, + "description": "MaintenanceSummary represents metadata for a maintenance window (used in list responses)." + }, + "openstatus.maintenance.v1.UpdateMaintenanceRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "id", + "minLength": 1, + "description": "ID of the maintenance to update (required)." }, - "degradedAt": { - "type": ["integer", "string", "null"], - "title": "degraded_at", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." + "title": { + "type": ["string", "null"], + "title": "title", + "maxLength": 256, + "minLength": 1, + "description": "New title for the maintenance (optional)." }, - "retry": { - "type": ["integer", "string"], - "title": "retry", - "maximum": 10, - "minimum": 0, - "format": "int64", - "description": "Number of retry attempts (0-10, defaults to 3)." + "message": { + "type": ["string", "null"], + "title": "message", + "description": "New message for the maintenance (optional)." }, - "followRedirects": { - "type": ["boolean", "null"], - "title": "follow_redirects", - "description": "Whether to follow HTTP redirects (defaults to true when not specified)." + "from": { + "type": ["string", "null"], + "title": "from", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", + "description": "New start time (RFC 3339 format, optional)." }, - "headers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.Headers" - }, - "title": "headers", - "maxItems": 20, - "description": "Custom headers for the request." + "to": { + "type": ["string", "null"], + "title": "to", + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", + "description": "New end time (RFC 3339 format, optional)." }, - "statusCodeAssertions": { + "pageId": { + "type": ["string", "null"], + "title": "page_id", + "description": "Deprecated: page_id is now derived from page_component_ids.", + "deprecated": true + }, + "pageComponentIds": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.StatusCodeAssertion" + "type": "string" }, - "title": "status_code_assertions", - "maxItems": 10, - "description": "Status code assertions for the response." + "title": "page_component_ids", + "description": "New list of page component IDs (optional, replaces existing list)." }, - "bodyAssertions": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.BodyAssertion" + "updatePageComponentIds": { + "type": ["boolean", "null"], + "title": "update_page_component_ids", + "description": "Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved." + } + }, + "title": "UpdateMaintenanceRequest", + "additionalProperties": false, + "description": "UpdateMaintenanceRequest is the request to update a maintenance window." + }, + "openstatus.maintenance.v1.UpdateMaintenanceResponse": { + "type": "object", + "properties": { + "maintenance": { + "title": "maintenance", + "description": "The updated maintenance.", + "$ref": "#/components/schemas/openstatus.maintenance.v1.Maintenance" + } + }, + "title": "UpdateMaintenanceResponse", + "additionalProperties": false, + "description": "UpdateMaintenanceResponse is the response after updating a maintenance window." + }, + "openstatus.monitor.v1.BodyAssertion": { + "type": "object", + "properties": { + "target": { + "type": "string", + "title": "target", + "description": "Target value to compare against." + }, + "comparator": { + "not": { + "enum": ["STRING_COMPARATOR_UNSPECIFIED"] }, - "title": "body_assertions", - "maxItems": 10, - "description": "Body content assertions for the response." + "title": "comparator", + "description": "Comparison operation (required, must not be UNSPECIFIED).", + "$ref": "#/components/schemas/openstatus.monitor.v1.StringComparator" + } + }, + "title": "BodyAssertion", + "additionalProperties": false, + "description": "BodyAssertion defines an assertion for response body content." + }, + "openstatus.monitor.v1.CreateDNSMonitorRequest": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "Monitor configuration (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + } + }, + "title": "CreateDNSMonitorRequest", + "required": ["monitor"], + "additionalProperties": false, + "description": "CreateDNSMonitorRequest is the request to create a new DNS monitor." + }, + "openstatus.monitor.v1.CreateDNSMonitorResponse": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "The created monitor with assigned ID.", + "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + } + }, + "title": "CreateDNSMonitorResponse", + "additionalProperties": false, + "description": "CreateDNSMonitorResponse is the response after creating a DNS monitor." + }, + "openstatus.monitor.v1.CreateGRPCMonitorRequest": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "Monitor configuration (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" + } + }, + "title": "CreateGRPCMonitorRequest", + "required": ["monitor"], + "additionalProperties": false, + "description": "CreateGRPCMonitorRequest is the request to create a new gRPC monitor." + }, + "openstatus.monitor.v1.CreateGRPCMonitorResponse": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "The created monitor with assigned ID.", + "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" + } + }, + "title": "CreateGRPCMonitorResponse", + "additionalProperties": false, + "description": "CreateGRPCMonitorResponse is the response after creating a gRPC monitor." + }, + "openstatus.monitor.v1.CreateHTTPMonitorRequest": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "Monitor configuration (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" + } + }, + "title": "CreateHTTPMonitorRequest", + "required": ["monitor"], + "additionalProperties": false, + "description": "CreateHTTPMonitorRequest is the request to create a new HTTP monitor." + }, + "openstatus.monitor.v1.CreateHTTPMonitorResponse": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "The created monitor with assigned ID.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" + } + }, + "title": "CreateHTTPMonitorResponse", + "additionalProperties": false, + "description": "CreateHTTPMonitorResponse is the response after creating an HTTP monitor." + }, + "openstatus.monitor.v1.CreateICMPMonitorRequest": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "Monitor configuration (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + } + }, + "title": "CreateICMPMonitorRequest", + "required": ["monitor"], + "additionalProperties": false, + "description": "CreateICMPMonitorRequest is the request to create a new ICMP monitor." + }, + "openstatus.monitor.v1.CreateICMPMonitorResponse": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "The created monitor with assigned ID.", + "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + } + }, + "title": "CreateICMPMonitorResponse", + "additionalProperties": false, + "description": "CreateICMPMonitorResponse is the response after creating an ICMP monitor." + }, + "openstatus.monitor.v1.CreateTCPMonitorRequest": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "Monitor configuration (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + } + }, + "title": "CreateTCPMonitorRequest", + "required": ["monitor"], + "additionalProperties": false, + "description": "CreateTCPMonitorRequest is the request to create a new TCP monitor." + }, + "openstatus.monitor.v1.CreateTCPMonitorResponse": { + "type": "object", + "properties": { + "monitor": { + "title": "monitor", + "description": "The created monitor with assigned ID.", + "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + } + }, + "title": "CreateTCPMonitorResponse", + "additionalProperties": false, + "description": "CreateTCPMonitorResponse is the response after creating a TCP monitor." + }, + "openstatus.monitor.v1.DNSMonitor": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "id", + "description": "Unique identifier for the monitor (output only for create requests)." }, - "headerAssertions": { + "name": { + "type": "string", + "examples": ["DNS Resolution Check"], + "title": "name", + "maxLength": 256, + "minLength": 1, + "description": "Name of the monitor (required, max 256 characters)." + }, + "uri": { + "type": "string", + "examples": ["example.com"], + "title": "uri", + "maxLength": 2048, + "minLength": 1, + "description": "Domain to resolve (required, max 2048 characters)." + }, + "periodicity": { + "not": { + "enum": ["PERIODICITY_UNSPECIFIED"] + }, + "title": "periodicity", + "description": "Check periodicity (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" + }, + "timeout": { + "type": ["integer", "string"], + "title": "timeout", + "maximum": 120000, + "minimum": 0, + "format": "int64", + "description": "Timeout in milliseconds (0-120000, defaults to 45000)." + }, + "degradedAt": { + "type": ["integer", "string", "null"], + "title": "degraded_at", + "maximum": 120000, + "minimum": 0, + "format": "int64", + "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." + }, + "retry": { + "type": ["integer", "string"], + "title": "retry", + "maximum": 10, + "minimum": 0, + "format": "int64", + "description": "Number of retry attempts (0-10, defaults to 3)." + }, + "recordAssertions": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.HeaderAssertion" + "$ref": "#/components/schemas/openstatus.monitor.v1.RecordAssertion" }, - "title": "header_assertions", + "title": "record_assertions", "maxItems": 10, - "description": "Header assertions for the response." + "description": "DNS record assertions for validation." }, "description": { "type": ["string", "null"], @@ -1333,345 +1741,291 @@ "readOnly": true } }, - "title": "HTTPMonitor", + "title": "DNSMonitor", "additionalProperties": false, - "description": "HTTPMonitor defines the configuration for an HTTP monitor." + "description": "DNSMonitor defines the configuration for a DNS monitor." }, - "openstatus.monitor.v1.HTTPResponseLogDetail": { + "openstatus.monitor.v1.DeleteMonitorRequest": { "type": "object", "properties": { - "log": { - "title": "log", - "description": "Compact response log fields.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem" - }, - "url": { + "id": { "type": "string", - "title": "url", - "description": "Checked URL." - }, - "error": { - "type": "boolean", - "title": "error", - "description": "Whether the check errored." - }, - "message": { - "type": ["string", "null"], - "title": "message", - "description": "Error message, when present." - }, - "headers": { - "type": "object", - "title": "headers", - "additionalProperties": { - "type": "string", - "title": "value" - }, - "description": "Redacted response headers." - }, - "assertions": { - "type": ["string", "null"], - "title": "assertions", - "description": "Serialized assertions used for the check." + "title": "id", + "minLength": 1, + "description": "Monitor ID to delete (required)." } }, - "title": "HTTPResponseLogDetail", + "title": "DeleteMonitorRequest", "additionalProperties": false, - "description": "HTTPResponseLogDetail contains full response log debugging data." + "description": "DeleteMonitorRequest is the request to delete a monitor." }, - "openstatus.monitor.v1.HTTPResponseLogDetail.HeadersEntry": { + "openstatus.monitor.v1.DeleteMonitorResponse": { "type": "object", "properties": { - "key": { - "type": "string", - "title": "key" - }, - "value": { - "type": "string", - "title": "value" + "success": { + "type": "boolean", + "title": "success", + "description": "Whether the deletion was successful." } }, - "title": "HeadersEntry", - "additionalProperties": false + "title": "DeleteMonitorResponse", + "additionalProperties": false, + "description": "DeleteMonitorResponse is the response after deleting a monitor." }, - "openstatus.monitor.v1.HTTPResponseLogListItem": { + "openstatus.monitor.v1.GRPCMonitor": { "type": "object", "properties": { "id": { - "type": ["string", "null"], + "type": "string", "title": "id", - "description": "Response log ID." - }, - "latency": { - "type": "integer", - "title": "latency", - "format": "int32", - "description": "Latency in milliseconds." - }, - "statusCode": { - "type": ["integer", "null"], - "title": "status_code", - "format": "int32", - "description": "HTTP status code." + "description": "Unique identifier for the monitor (output only for create requests)." }, - "monitorId": { + "name": { "type": "string", - "title": "monitor_id", - "description": "Monitor ID." + "examples": ["Checkout gRPC"], + "title": "name", + "maxLength": 256, + "minLength": 1, + "description": "Name of the monitor (required, max 256 characters)." }, - "requestStatus": { - "title": "request_status", - "description": "Request status classification.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogRequestStatus" + "uri": { + "type": "string", + "examples": ["api.example.com:443"], + "title": "uri", + "maxLength": 2048, + "minLength": 1, + "pattern": "^(\\[[0-9a-fA-F:]+\\]|[^:/\\s]+):[0-9]{1,5}$", + "description": "Target in \"host:port\" form. IPv6 addresses must be bracketed." }, - "region": { - "title": "region", - "description": "Region where the check ran.", - "$ref": "#/components/schemas/openstatus.monitor.v1.Region" + "periodicity": { + "not": { + "enum": ["PERIODICITY_UNSPECIFIED"] + }, + "title": "periodicity", + "description": "Check periodicity (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" }, - "cronTimestamp": { + "timeout": { "type": ["integer", "string"], - "title": "cron_timestamp", + "title": "timeout", + "maximum": 120000, + "minimum": 0, "format": "int64", - "description": "Cron bucket timestamp in Unix milliseconds." + "description": "Timeout in milliseconds (0-120000, defaults to 45000)." }, - "trigger": { - "title": "trigger", - "description": "Check trigger.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTrigger" + "degradedAt": { + "type": ["integer", "string", "null"], + "title": "degraded_at", + "maximum": 120000, + "minimum": 0, + "format": "int64", + "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." }, - "timestamp": { + "retry": { "type": ["integer", "string"], - "title": "timestamp", + "title": "retry", + "maximum": 10, + "minimum": 0, "format": "int64", - "description": "Response timestamp in Unix milliseconds." + "description": "Number of retry attempts (0-10, defaults to 3)." }, - "timing": { + "description": { + "type": ["string", "null"], + "title": "description", + "maxLength": 1024, + "description": "Description of the monitor (optional)." + }, + "active": { + "type": ["boolean", "null"], + "title": "active", + "description": "Whether the monitor is active (defaults to false)." + }, + "public": { + "type": ["boolean", "null"], + "title": "public", + "description": "Whether the monitor is publicly visible (defaults to false)." + }, + "regions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.Region" + }, + "title": "regions", + "maxItems": 28, + "description": "Geographic regions to run checks from." + }, + "openTelemetry": { + "title": "open_telemetry", + "description": "OpenTelemetry configuration for exporting metrics.", + "$ref": "#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig" + }, + "status": { + "title": "status", + "description": "Current operational status of the monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" + }, + "privateLocationIds": { + "type": "array", + "items": { + "type": "string", + "readOnly": true + }, + "title": "private_location_ids", + "description": "IDs of private locations that run this monitor. Read-only.", + "readOnly": true + }, + "service": { + "type": ["string", "null"], + "examples": ["checkout.v1.CheckoutService"], + "title": "service", + "maxLength": 512, + "description": "Service name passed to Health/Check. Empty means overall server health." + }, + "tlsMode": { "oneOf": [ { - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTiming" + "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCTlsMode" }, { "type": "null" } ], - "title": "timing", - "description": "Timing phases." - } - }, - "title": "HTTPResponseLogListItem", - "additionalProperties": false, - "description": "HTTPResponseLogListItem is a compact response log entry." - }, - "openstatus.monitor.v1.HTTPResponseLogPagination": { - "type": "object", - "properties": { - "limit": { - "type": "integer", - "title": "limit", - "format": "int32", - "description": "Requested page size." - }, - "offset": { - "type": "integer", - "title": "offset", - "format": "int32", - "description": "Requested offset." - }, - "hasMore": { - "type": "boolean", - "title": "has_more", - "description": "Whether more logs are available." + "title": "tls_mode", + "description": "How the connection to the target is secured. Defaults to TLS." }, - "nextOffset": { - "type": ["integer", "null"], - "title": "next_offset", - "format": "int32", - "description": "Next offset if more logs are available." + "metadata": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.Headers" + }, + "title": "metadata", + "maxItems": 20, + "description": "Metadata sent with the health check request." } }, - "title": "HTTPResponseLogPagination", + "title": "GRPCMonitor", "additionalProperties": false, - "description": "HTTPResponseLogPagination contains offset pagination metadata." + "description": "GRPCMonitor defines the configuration for a gRPC health check monitor.\n The probe calls grpc.health.v1.Health/Check on the target." }, - "openstatus.monitor.v1.HTTPResponseLogRequestStatus": { + "openstatus.monitor.v1.GRPCTlsMode": { "type": "string", - "title": "HTTPResponseLogRequestStatus", + "title": "GRPCTlsMode", "enum": [ - "HTTP_RESPONSE_LOG_REQUEST_STATUS_UNSPECIFIED", - "HTTP_RESPONSE_LOG_REQUEST_STATUS_SUCCESS", - "HTTP_RESPONSE_LOG_REQUEST_STATUS_ERROR", - "HTTP_RESPONSE_LOG_REQUEST_STATUS_DEGRADED" + "GRPC_TLS_MODE_UNSPECIFIED", + "GRPC_TLS_MODE_TLS", + "GRPC_TLS_MODE_PLAINTEXT", + "GRPC_TLS_MODE_TLS_INSECURE" ], - "description": "HTTPResponseLogRequestStatus is the result classification for an HTTP response log." + "description": "GRPCTlsMode selects how the probe secures its connection to the target." }, - "openstatus.monitor.v1.HTTPResponseLogTiming": { + "openstatus.monitor.v1.GetMonitorHTTPResponseLogRequest": { "type": "object", "properties": { - "dns": { - "type": "integer", - "title": "dns", - "format": "int32", - "description": "DNS lookup duration." - }, - "connect": { - "type": "integer", - "title": "connect", - "format": "int32", - "description": "TCP connection duration." - }, - "tls": { - "type": "integer", - "title": "tls", - "format": "int32", - "description": "TLS handshake duration." - }, - "ttfb": { - "type": "integer", - "title": "ttfb", - "format": "int32", - "description": "Time to first byte duration." + "id": { + "type": "string", + "title": "id", + "minLength": 1, + "description": "Monitor ID to get a response log for (required)." }, - "transfer": { - "type": "integer", - "title": "transfer", - "format": "int32", - "description": "Response transfer duration." + "logId": { + "type": "string", + "title": "log_id", + "minLength": 1, + "description": "Response log ID to retrieve (required)." } }, - "title": "HTTPResponseLogTiming", + "title": "GetMonitorHTTPResponseLogRequest", "additionalProperties": false, - "description": "HTTPResponseLogTiming contains calculated timing phases in milliseconds." + "description": "GetMonitorHTTPResponseLogRequest is the request to get one response log." }, - "openstatus.monitor.v1.HTTPResponseLogTrigger": { - "type": "string", - "title": "HTTPResponseLogTrigger", - "enum": [ - "HTTP_RESPONSE_LOG_TRIGGER_UNSPECIFIED", - "HTTP_RESPONSE_LOG_TRIGGER_CRON", - "HTTP_RESPONSE_LOG_TRIGGER_API" - ], - "description": "HTTPResponseLogTrigger describes what started the monitor check." + "openstatus.monitor.v1.GetMonitorHTTPResponseLogResponse": { + "type": "object", + "properties": { + "log": { + "title": "log", + "description": "Response log details.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogDetail" + } + }, + "title": "GetMonitorHTTPResponseLogResponse", + "additionalProperties": false, + "description": "GetMonitorHTTPResponseLogResponse is the response containing one response log." }, - "openstatus.monitor.v1.HeaderAssertion": { + "openstatus.monitor.v1.GetMonitorRequest": { "type": "object", "properties": { - "target": { - "type": "string", - "title": "target", - "description": "Target value to compare against." - }, - "comparator": { - "not": { - "enum": ["STRING_COMPARATOR_UNSPECIFIED"] - }, - "title": "comparator", - "description": "Comparison operation (required, must not be UNSPECIFIED).", - "$ref": "#/components/schemas/openstatus.monitor.v1.StringComparator" - }, - "key": { + "id": { "type": "string", - "title": "key", + "title": "id", "minLength": 1, - "description": "Header key to check (required)." + "description": "Monitor ID to retrieve (required)." } }, - "title": "HeaderAssertion", + "title": "GetMonitorRequest", "additionalProperties": false, - "description": "HeaderAssertion defines an assertion for response headers." + "description": "GetMonitorRequest is the request to get a single monitor by ID." }, - "openstatus.monitor.v1.Headers": { + "openstatus.monitor.v1.GetMonitorResponse": { "type": "object", "properties": { - "key": { + "monitor": { + "title": "monitor", + "description": "The monitor configuration (one of HTTP, TCP, DNS, ICMP, or gRPC).", + "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorConfig" + } + }, + "title": "GetMonitorResponse", + "additionalProperties": false, + "description": "GetMonitorResponse is the response containing the monitor." + }, + "openstatus.monitor.v1.GetMonitorStatusRequest": { + "type": "object", + "properties": { + "id": { "type": "string", - "examples": ["Authorization"], - "title": "key", + "title": "id", "minLength": 1, - "description": "Header name." - }, - "value": { - "type": "string", - "examples": ["Bearer token123"], - "title": "value", - "description": "Header value." + "description": "Monitor ID to get status for (required)." } }, - "title": "Headers", + "title": "GetMonitorStatusRequest", "additionalProperties": false, - "description": "Headers represents a key-value pair for HTTP headers." + "description": "GetMonitorStatusRequest is the request to get the status of all regions for a monitor." }, - "openstatus.monitor.v1.ICMPMonitor": { + "openstatus.monitor.v1.GetMonitorStatusResponse": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "description": "Unique identifier for the monitor (output only for create requests)." - }, - "name": { - "type": "string", - "examples": ["Ping Gateway"], - "title": "name", - "maxLength": 256, - "minLength": 1, - "description": "Name of the monitor (required, max 256 characters)." + "description": "Monitor ID." }, - "uri": { + "regions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.RegionStatus" + }, + "title": "regions", + "description": "Status for each region." + } + }, + "title": "GetMonitorStatusResponse", + "additionalProperties": false, + "description": "GetMonitorStatusResponse is the response containing the status of all regions for a monitor." + }, + "openstatus.monitor.v1.GetMonitorSummaryRequest": { + "type": "object", + "properties": { + "id": { "type": "string", - "examples": ["1.1.1.1"], - "title": "uri", - "maxLength": 2048, + "title": "id", "minLength": 1, - "description": "URI to monitor in format \"host or IP\" (required, max 2048 characters)." - }, - "periodicity": { - "not": { - "enum": ["PERIODICITY_UNSPECIFIED"] - }, - "title": "periodicity", - "description": "Check periodicity (required).", - "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" - }, - "timeout": { - "type": ["integer", "string"], - "title": "timeout", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Timeout in milliseconds (0-120000, defaults to 45000)." - }, - "degradedAt": { - "type": ["integer", "string", "null"], - "title": "degraded_at", - "maximum": 120000, - "minimum": 0, - "format": "int64", - "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." - }, - "retry": { - "type": ["integer", "string"], - "title": "retry", - "maximum": 10, - "minimum": 0, - "format": "int64", - "description": "Number of retry attempts (0-10, defaults to 3)." - }, - "description": { - "type": ["string", "null"], - "title": "description", - "maxLength": 1024, - "description": "Description of the monitor (optional)." - }, - "active": { - "type": ["boolean", "null"], - "title": "active", - "description": "Whether the monitor is active (defaults to false)." + "description": "Monitor ID to get summary for (required)." }, - "public": { - "type": ["boolean", "null"], - "title": "public", - "description": "Whether the monitor is publicly visible (defaults to false)." + "timeRange": { + "title": "time_range", + "description": "Time range for metrics aggregation (defaults to 1 day if unspecified).", + "$ref": "#/components/schemas/openstatus.monitor.v1.TimeRange" }, "regions": { "type": "array", @@ -1680,438 +2034,536 @@ }, "title": "regions", "maxItems": 28, - "description": "Geographic regions to run checks from." - }, - "openTelemetry": { - "title": "open_telemetry", - "description": "OpenTelemetry configuration for exporting metrics.", - "$ref": "#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig" - }, - "status": { - "title": "status", - "description": "Current operational status of the monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" - }, - "privateLocationIds": { - "type": "array", - "items": { - "type": "string", - "readOnly": true - }, - "title": "private_location_ids", - "description": "IDs of private locations that run this monitor. Read-only.", - "readOnly": true + "description": "Optional filter by regions. If empty, returns metrics for all regions." } }, - "title": "ICMPMonitor", + "title": "GetMonitorSummaryRequest", "additionalProperties": false, - "description": "ICMPMonitor defines the configuration for a ICMP monitor." + "description": "GetMonitorSummaryRequest is the request to get aggregated metrics for a monitor." }, - "openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest": { + "openstatus.monitor.v1.GetMonitorSummaryResponse": { "type": "object", "properties": { "id": { "type": "string", "title": "id", - "minLength": 1, - "description": "Monitor ID to list response logs for (required)." + "description": "Monitor ID." }, - "fromTimestamp": { - "type": ["integer", "string", "null"], - "title": "from_timestamp", - "format": "int64", - "description": "Start of the response log window as Unix milliseconds within the 14-day retention window." + "lastPingAt": { + "type": "string", + "title": "last_ping_at", + "description": "Timestamp of the last check in RFC 3339 format." }, - "toTimestamp": { - "type": ["integer", "string", "null"], - "title": "to_timestamp", + "totalSuccessful": { + "type": ["integer", "string"], + "title": "total_successful", "format": "int64", - "description": "End of the response log window as Unix milliseconds within the 14-day retention window." + "description": "Total number of successful requests." }, - "limit": { - "type": ["integer", "null"], - "title": "limit", - "maximum": 100, - "minimum": 1, - "format": "int32", - "description": "Maximum number of logs to return (1-100, defaults to 25)." + "totalDegraded": { + "type": ["integer", "string"], + "title": "total_degraded", + "format": "int64", + "description": "Total number of degraded requests." }, - "offset": { - "type": ["integer", "null"], - "title": "offset", - "minimum": 0, - "format": "int32", - "description": "Number of logs to skip for pagination (defaults to 0)." - } - }, - "title": "ListMonitorHTTPResponseLogsRequest", - "additionalProperties": false, - "description": "ListMonitorHTTPResponseLogsRequest is the request to list response logs within the 14-day HTTP response-log window." - }, - "openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse": { - "type": "object", - "properties": { - "logs": { + "totalFailed": { + "type": ["integer", "string"], + "title": "total_failed", + "format": "int64", + "description": "Total number of failed requests." + }, + "p50": { + "type": ["integer", "string"], + "title": "p50", + "format": "int64", + "description": "50th percentile (median) latency in milliseconds." + }, + "p75": { + "type": ["integer", "string"], + "title": "p75", + "format": "int64", + "description": "75th percentile latency in milliseconds." + }, + "p90": { + "type": ["integer", "string"], + "title": "p90", + "format": "int64", + "description": "90th percentile latency in milliseconds." + }, + "p95": { + "type": ["integer", "string"], + "title": "p95", + "format": "int64", + "description": "95th percentile latency in milliseconds." + }, + "p99": { + "type": ["integer", "string"], + "title": "p99", + "format": "int64", + "description": "99th percentile latency in milliseconds." + }, + "timeRange": { + "title": "time_range", + "description": "Time range used for the metrics.", + "$ref": "#/components/schemas/openstatus.monitor.v1.TimeRange" + }, + "regions": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem" + "$ref": "#/components/schemas/openstatus.monitor.v1.Region" }, - "title": "logs", - "description": "Response logs." - }, - "pagination": { - "title": "pagination", - "description": "Pagination metadata.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogPagination" + "title": "regions", + "description": "Regions included in the metrics." } }, - "title": "ListMonitorHTTPResponseLogsResponse", + "title": "GetMonitorSummaryResponse", "additionalProperties": false, - "description": "ListMonitorHTTPResponseLogsResponse is the response containing response logs." + "description": "GetMonitorSummaryResponse is the response containing aggregated metrics for a monitor." }, - "openstatus.monitor.v1.ListMonitorsRequest": { + "openstatus.monitor.v1.HTTPMethod": { + "type": "string", + "title": "HTTPMethod", + "enum": [ + "HTTP_METHOD_UNSPECIFIED", + "HTTP_METHOD_GET", + "HTTP_METHOD_POST", + "HTTP_METHOD_HEAD", + "HTTP_METHOD_PUT", + "HTTP_METHOD_PATCH", + "HTTP_METHOD_DELETE", + "HTTP_METHOD_TRACE", + "HTTP_METHOD_CONNECT", + "HTTP_METHOD_OPTIONS" + ], + "description": "HTTP methods supported for monitors." + }, + "openstatus.monitor.v1.HTTPMonitor": { "type": "object", "properties": { - "limit": { - "type": ["integer", "null"], - "title": "limit", - "maximum": 100, - "minimum": 1, - "format": "int32", - "description": "Maximum number of monitors to return (1-100, defaults to 50)." + "id": { + "type": "string", + "title": "id", + "description": "Unique identifier for the monitor (output only for create requests)." }, - "offset": { - "type": ["integer", "null"], - "title": "offset", + "name": { + "type": "string", + "examples": ["Production API Health Check"], + "title": "name", + "maxLength": 256, + "minLength": 1, + "description": "Name of the monitor (required, max 256 characters)." + }, + "url": { + "type": "string", + "examples": ["https://api.example.com/health"], + "title": "url", + "maxLength": 2048, + "minLength": 1, + "format": "uri", + "description": "URL to monitor (required, max 2048 characters)." + }, + "periodicity": { + "not": { + "enum": ["PERIODICITY_UNSPECIFIED"] + }, + "title": "periodicity", + "description": "Check periodicity (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" + }, + "method": { + "not": { + "enum": ["HTTP_METHOD_UNSPECIFIED"] + }, + "title": "method", + "description": "HTTP method to use (defaults to GET).", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMethod" + }, + "body": { + "type": "string", + "examples": ["map[key:value]"], + "title": "body", + "description": "Request body (optional)." + }, + "timeout": { + "type": ["integer", "string"], + "title": "timeout", + "maximum": 120000, "minimum": 0, - "format": "int32", - "description": "Number of monitors to skip for pagination (defaults to 0)." - } - }, - "title": "ListMonitorsRequest", - "additionalProperties": false, - "description": "ListMonitorsRequest is the request to list monitors." - }, - "openstatus.monitor.v1.ListMonitorsResponse": { - "type": "object", - "properties": { - "httpMonitors": { + "format": "int64", + "description": "Timeout in milliseconds (0-120000, defaults to 45000)." + }, + "degradedAt": { + "type": ["integer", "string", "null"], + "title": "degraded_at", + "maximum": 120000, + "minimum": 0, + "format": "int64", + "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." + }, + "retry": { + "type": ["integer", "string"], + "title": "retry", + "maximum": 10, + "minimum": 0, + "format": "int64", + "description": "Number of retry attempts (0-10, defaults to 3)." + }, + "followRedirects": { + "type": ["boolean", "null"], + "title": "follow_redirects", + "description": "Whether to follow HTTP redirects (defaults to true when not specified)." + }, + "headers": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" + "$ref": "#/components/schemas/openstatus.monitor.v1.Headers" }, - "title": "http_monitors", - "description": "HTTP monitors in the workspace." + "title": "headers", + "maxItems": 20, + "description": "Custom headers for the request." }, - "tcpMonitors": { + "statusCodeAssertions": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + "$ref": "#/components/schemas/openstatus.monitor.v1.StatusCodeAssertion" }, - "title": "tcp_monitors", - "description": "TCP monitors in the workspace." + "title": "status_code_assertions", + "maxItems": 10, + "description": "Status code assertions for the response." }, - "dnsMonitors": { + "bodyAssertions": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + "$ref": "#/components/schemas/openstatus.monitor.v1.BodyAssertion" }, - "title": "dns_monitors", - "description": "DNS monitors in the workspace." + "title": "body_assertions", + "maxItems": 10, + "description": "Body content assertions for the response." }, - "icmpMonitors": { + "headerAssertions": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + "$ref": "#/components/schemas/openstatus.monitor.v1.HeaderAssertion" }, - "title": "icmp_monitors", - "description": "ICMP monitors in the workspace." + "title": "header_assertions", + "maxItems": 10, + "description": "Header assertions for the response." }, - "grpcMonitors": { + "description": { + "type": ["string", "null"], + "title": "description", + "maxLength": 1024, + "description": "Description of the monitor (optional)." + }, + "active": { + "type": ["boolean", "null"], + "title": "active", + "description": "Whether the monitor is active (defaults to false)." + }, + "public": { + "type": ["boolean", "null"], + "title": "public", + "description": "Whether the monitor is publicly visible (defaults to false)." + }, + "regions": { "type": "array", "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" + "$ref": "#/components/schemas/openstatus.monitor.v1.Region" }, - "title": "grpc_monitors", - "description": "gRPC monitors in the workspace." + "title": "regions", + "maxItems": 28, + "description": "Geographic regions to run checks from." }, - "totalSize": { - "type": "integer", - "title": "total_size", - "format": "int32", - "description": "Total number of monitors across all types." + "openTelemetry": { + "title": "open_telemetry", + "description": "OpenTelemetry configuration for exporting metrics.", + "$ref": "#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig" + }, + "status": { + "title": "status", + "description": "Current operational status of the monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" + }, + "privateLocationIds": { + "type": "array", + "items": { + "type": "string", + "readOnly": true + }, + "title": "private_location_ids", + "description": "IDs of private locations that run this monitor. Read-only.", + "readOnly": true } }, - "title": "ListMonitorsResponse", + "title": "HTTPMonitor", "additionalProperties": false, - "description": "ListMonitorsResponse is the response containing a list of monitors." + "description": "HTTPMonitor defines the configuration for an HTTP monitor." }, - "openstatus.monitor.v1.MonitorConfig": { + "openstatus.monitor.v1.HTTPResponseLogDetail": { "type": "object", - "oneOf": [ - { - "type": "object", - "properties": { - "dns": { - "title": "dns", - "description": "DNS monitor configuration.", - "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" - } - }, - "title": "dns", - "required": ["dns"] + "properties": { + "log": { + "title": "log", + "description": "Compact response log fields.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem" }, - { - "type": "object", - "properties": { - "grpc": { - "title": "grpc", - "description": "gRPC monitor configuration.", - "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" - } - }, - "title": "grpc", - "required": ["grpc"] + "url": { + "type": "string", + "title": "url", + "description": "Checked URL." }, - { - "type": "object", - "properties": { - "http": { - "title": "http", - "description": "HTTP monitor configuration.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" - } - }, - "title": "http", - "required": ["http"] + "error": { + "type": "boolean", + "title": "error", + "description": "Whether the check errored." }, - { - "type": "object", - "properties": { - "icmp": { - "title": "icmp", - "description": "ICMP monitor configuration.", - "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" - } - }, - "title": "icmp", - "required": ["icmp"] + "message": { + "type": ["string", "null"], + "title": "message", + "description": "Error message, when present." }, - { + "headers": { "type": "object", - "properties": { - "tcp": { - "title": "tcp", - "description": "TCP monitor configuration.", - "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" - } + "title": "headers", + "additionalProperties": { + "type": "string", + "title": "value" }, - "title": "tcp", - "required": ["tcp"] + "description": "Redacted response headers." + }, + "assertions": { + "type": ["string", "null"], + "title": "assertions", + "description": "Serialized assertions used for the check." } - ], - "title": "MonitorConfig", + }, + "title": "HTTPResponseLogDetail", "additionalProperties": false, - "description": "MonitorConfig represents the type-specific configuration for a monitor." - }, - "openstatus.monitor.v1.MonitorStatus": { - "type": "string", - "title": "MonitorStatus", - "enum": [ - "MONITOR_STATUS_UNSPECIFIED", - "MONITOR_STATUS_ACTIVE", - "MONITOR_STATUS_DEGRADED", - "MONITOR_STATUS_ERROR" - ], - "description": "MonitorStatus represents the operational status of a monitor." - }, - "openstatus.monitor.v1.NumberComparator": { - "type": "string", - "title": "NumberComparator", - "enum": [ - "NUMBER_COMPARATOR_UNSPECIFIED", - "NUMBER_COMPARATOR_EQUAL", - "NUMBER_COMPARATOR_NOT_EQUAL", - "NUMBER_COMPARATOR_GREATER_THAN", - "NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL", - "NUMBER_COMPARATOR_LESS_THAN", - "NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL" - ], - "description": "NumberComparator defines comparison operations for numeric values." + "description": "HTTPResponseLogDetail contains full response log debugging data." }, - "openstatus.monitor.v1.OpenTelemetryConfig": { + "openstatus.monitor.v1.HTTPResponseLogDetail.HeadersEntry": { "type": "object", "properties": { - "endpoint": { + "key": { "type": "string", - "title": "endpoint", - "maxLength": 2048, - "description": "OTEL endpoint URL." + "title": "key" }, - "headers": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.monitor.v1.Headers" - }, - "title": "headers", - "maxItems": 20, - "description": "Custom headers for OTEL requests." + "value": { + "type": "string", + "title": "value" } }, - "title": "OpenTelemetryConfig", - "additionalProperties": false, - "description": "OpenTelemetry configuration for exporting metrics." - }, - "openstatus.monitor.v1.Periodicity": { - "type": "string", - "title": "Periodicity", - "enum": [ - "PERIODICITY_UNSPECIFIED", - "PERIODICITY_30S", - "PERIODICITY_1M", - "PERIODICITY_5M", - "PERIODICITY_10M", - "PERIODICITY_30M", - "PERIODICITY_1H" - ], - "description": "Monitor periodicity options." + "title": "HeadersEntry", + "additionalProperties": false }, - "openstatus.monitor.v1.RecordAssertion": { + "openstatus.monitor.v1.HTTPResponseLogListItem": { "type": "object", "properties": { - "record": { - "type": "string", - "title": "record", - "enum": ["A", "AAAA", "CNAME", "MX", "TXT"], - "description": "DNS record type (e.g., \"A\", \"AAAA\", \"CNAME\", \"MX\", \"TXT\")." + "id": { + "type": ["string", "null"], + "title": "id", + "description": "Response log ID." }, - "comparator": { - "not": { - "enum": ["RECORD_COMPARATOR_UNSPECIFIED"] - }, - "title": "comparator", - "description": "Comparison operation (required, must not be UNSPECIFIED).", - "$ref": "#/components/schemas/openstatus.monitor.v1.RecordComparator" + "latency": { + "type": "integer", + "title": "latency", + "format": "int32", + "description": "Latency in milliseconds." }, - "target": { + "statusCode": { + "type": ["integer", "null"], + "title": "status_code", + "format": "int32", + "description": "HTTP status code." + }, + "monitorId": { "type": "string", - "title": "target", - "description": "Target value to compare against." + "title": "monitor_id", + "description": "Monitor ID." + }, + "requestStatus": { + "title": "request_status", + "description": "Request status classification.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogRequestStatus" + }, + "region": { + "title": "region", + "description": "Region where the check ran.", + "$ref": "#/components/schemas/openstatus.monitor.v1.Region" + }, + "cronTimestamp": { + "type": ["integer", "string"], + "title": "cron_timestamp", + "format": "int64", + "description": "Cron bucket timestamp in Unix milliseconds." + }, + "trigger": { + "title": "trigger", + "description": "Check trigger.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTrigger" + }, + "timestamp": { + "type": ["integer", "string"], + "title": "timestamp", + "format": "int64", + "description": "Response timestamp in Unix milliseconds." + }, + "timing": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogTiming" + }, + { + "type": "null" + } + ], + "title": "timing", + "description": "Timing phases." } }, - "title": "RecordAssertion", + "title": "HTTPResponseLogListItem", "additionalProperties": false, - "description": "RecordAssertion defines an assertion for DNS records." + "description": "HTTPResponseLogListItem is a compact response log entry." }, - "openstatus.monitor.v1.RecordComparator": { - "type": "string", - "title": "RecordComparator", - "enum": [ - "RECORD_COMPARATOR_UNSPECIFIED", - "RECORD_COMPARATOR_EQUAL", - "RECORD_COMPARATOR_NOT_EQUAL", - "RECORD_COMPARATOR_CONTAINS", - "RECORD_COMPARATOR_NOT_CONTAINS" - ], - "description": "RecordComparator defines comparison operations for DNS records." + "openstatus.monitor.v1.HTTPResponseLogPagination": { + "type": "object", + "properties": { + "limit": { + "type": "integer", + "title": "limit", + "format": "int32", + "description": "Requested page size." + }, + "offset": { + "type": "integer", + "title": "offset", + "format": "int32", + "description": "Requested offset." + }, + "hasMore": { + "type": "boolean", + "title": "has_more", + "description": "Whether more logs are available." + }, + "nextOffset": { + "type": ["integer", "null"], + "title": "next_offset", + "format": "int32", + "description": "Next offset if more logs are available." + } + }, + "title": "HTTPResponseLogPagination", + "additionalProperties": false, + "description": "HTTPResponseLogPagination contains offset pagination metadata." }, - "openstatus.monitor.v1.Region": { + "openstatus.monitor.v1.HTTPResponseLogRequestStatus": { "type": "string", - "title": "Region", + "title": "HTTPResponseLogRequestStatus", "enum": [ - "REGION_UNSPECIFIED", - "REGION_FLY_AMS", - "REGION_FLY_ARN", - "REGION_FLY_BOM", - "REGION_FLY_CDG", - "REGION_FLY_DFW", - "REGION_FLY_EWR", - "REGION_FLY_FRA", - "REGION_FLY_GRU", - "REGION_FLY_IAD", - "REGION_FLY_JNB", - "REGION_FLY_LAX", - "REGION_FLY_LHR", - "REGION_FLY_NRT", - "REGION_FLY_ORD", - "REGION_FLY_SJC", - "REGION_FLY_SIN", - "REGION_FLY_SYD", - "REGION_FLY_YYZ", - "REGION_KOYEB_FRA", - "REGION_KOYEB_PAR", - "REGION_KOYEB_SFO", - "REGION_KOYEB_SIN", - "REGION_KOYEB_TYO", - "REGION_KOYEB_WAS", - "REGION_RAILWAY_US_WEST2", - "REGION_RAILWAY_US_EAST4", - "REGION_RAILWAY_EUROPE_WEST4", - "REGION_RAILWAY_ASIA_SOUTHEAST1" + "HTTP_RESPONSE_LOG_REQUEST_STATUS_UNSPECIFIED", + "HTTP_RESPONSE_LOG_REQUEST_STATUS_SUCCESS", + "HTTP_RESPONSE_LOG_REQUEST_STATUS_ERROR", + "HTTP_RESPONSE_LOG_REQUEST_STATUS_DEGRADED" ], - "description": "Geographic regions where monitors can run checks from.\n REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN." + "description": "HTTPResponseLogRequestStatus is the result classification for an HTTP response log." }, - "openstatus.monitor.v1.RegionStatus": { + "openstatus.monitor.v1.HTTPResponseLogTiming": { "type": "object", "properties": { - "region": { - "title": "region", - "description": "The region identifier.", - "$ref": "#/components/schemas/openstatus.monitor.v1.Region" + "dns": { + "type": "integer", + "title": "dns", + "format": "int32", + "description": "DNS lookup duration." }, - "status": { - "title": "status", - "description": "The status of the monitor in this region.", - "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" + "connect": { + "type": "integer", + "title": "connect", + "format": "int32", + "description": "TCP connection duration." + }, + "tls": { + "type": "integer", + "title": "tls", + "format": "int32", + "description": "TLS handshake duration." + }, + "ttfb": { + "type": "integer", + "title": "ttfb", + "format": "int32", + "description": "Time to first byte duration." + }, + "transfer": { + "type": "integer", + "title": "transfer", + "format": "int32", + "description": "Response transfer duration." } }, - "title": "RegionStatus", + "title": "HTTPResponseLogTiming", "additionalProperties": false, - "description": "RegionStatus represents the status of a monitor in a specific region." + "description": "HTTPResponseLogTiming contains calculated timing phases in milliseconds." }, - "openstatus.monitor.v1.StatusCodeAssertion": { + "openstatus.monitor.v1.HTTPResponseLogTrigger": { + "type": "string", + "title": "HTTPResponseLogTrigger", + "enum": [ + "HTTP_RESPONSE_LOG_TRIGGER_UNSPECIFIED", + "HTTP_RESPONSE_LOG_TRIGGER_CRON", + "HTTP_RESPONSE_LOG_TRIGGER_API" + ], + "description": "HTTPResponseLogTrigger describes what started the monitor check." + }, + "openstatus.monitor.v1.HeaderAssertion": { "type": "object", "properties": { "target": { - "type": ["integer", "string"], + "type": "string", "title": "target", - "maximum": 599, - "minimum": 100, - "format": "int64", - "description": "Target status code to compare against (100-599)." + "description": "Target value to compare against." }, "comparator": { "not": { - "enum": ["NUMBER_COMPARATOR_UNSPECIFIED"] + "enum": ["STRING_COMPARATOR_UNSPECIFIED"] }, "title": "comparator", "description": "Comparison operation (required, must not be UNSPECIFIED).", - "$ref": "#/components/schemas/openstatus.monitor.v1.NumberComparator" + "$ref": "#/components/schemas/openstatus.monitor.v1.StringComparator" + }, + "key": { + "type": "string", + "title": "key", + "minLength": 1, + "description": "Header key to check (required)." } }, - "title": "StatusCodeAssertion", + "title": "HeaderAssertion", "additionalProperties": false, - "description": "StatusCodeAssertion defines an assertion for HTTP status codes." + "description": "HeaderAssertion defines an assertion for response headers." }, - "openstatus.monitor.v1.StringComparator": { - "type": "string", - "title": "StringComparator", - "enum": [ - "STRING_COMPARATOR_UNSPECIFIED", - "STRING_COMPARATOR_CONTAINS", - "STRING_COMPARATOR_NOT_CONTAINS", - "STRING_COMPARATOR_EQUAL", - "STRING_COMPARATOR_NOT_EQUAL", - "STRING_COMPARATOR_EMPTY", - "STRING_COMPARATOR_NOT_EMPTY", - "STRING_COMPARATOR_GREATER_THAN", - "STRING_COMPARATOR_GREATER_THAN_OR_EQUAL", - "STRING_COMPARATOR_LESS_THAN", - "STRING_COMPARATOR_LESS_THAN_OR_EQUAL" - ], - "description": "StringComparator defines comparison operations for string values." + "openstatus.monitor.v1.Headers": { + "type": "object", + "properties": { + "key": { + "type": "string", + "examples": ["Authorization"], + "title": "key", + "minLength": 1, + "description": "Header name." + }, + "value": { + "type": "string", + "examples": ["Bearer token123"], + "title": "value", + "description": "Header value." + } + }, + "title": "Headers", + "additionalProperties": false, + "description": "Headers represents a key-value pair for HTTP headers." }, - "openstatus.monitor.v1.TCPMonitor": { + "openstatus.monitor.v1.ICMPMonitor": { "type": "object", "properties": { "id": { @@ -2121,7 +2573,7 @@ }, "name": { "type": "string", - "examples": ["Database Connection Check"], + "examples": ["Ping Gateway"], "title": "name", "maxLength": 256, "minLength": 1, @@ -2129,11 +2581,11 @@ }, "uri": { "type": "string", - "examples": ["tcp://db.example.com:5432"], + "examples": ["1.1.1.1"], "title": "uri", "maxLength": 2048, "minLength": 1, - "description": "URI to monitor in format \"host:port\" (required, max 2048 characters)." + "description": "URI to monitor in format \"host or IP\" (required, max 2048 characters)." }, "periodicity": { "not": { @@ -2213,512 +2665,798 @@ "readOnly": true } }, - "title": "TCPMonitor", + "title": "ICMPMonitor", "additionalProperties": false, - "description": "TCPMonitor defines the configuration for a TCP monitor." - }, - "openstatus.monitor.v1.TimeRange": { - "type": "string", - "title": "TimeRange", - "enum": [ - "TIME_RANGE_UNSPECIFIED", - "TIME_RANGE_1D", - "TIME_RANGE_7D", - "TIME_RANGE_14D" - ], - "description": "TimeRange represents the time period for metrics aggregation." + "description": "ICMPMonitor defines the configuration for a ICMP monitor." }, - "openstatus.monitor.v1.TriggerMonitorRequest": { + "openstatus.monitor.v1.ListMonitorHTTPResponseLogsRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", "minLength": 1, - "description": "Monitor ID to trigger (required)." + "description": "Monitor ID to list response logs for (required)." + }, + "fromTimestamp": { + "type": ["integer", "string", "null"], + "title": "from_timestamp", + "format": "int64", + "description": "Start of the response log window as Unix milliseconds within the 14-day retention window." + }, + "toTimestamp": { + "type": ["integer", "string", "null"], + "title": "to_timestamp", + "format": "int64", + "description": "End of the response log window as Unix milliseconds within the 14-day retention window." + }, + "limit": { + "type": ["integer", "null"], + "title": "limit", + "maximum": 100, + "minimum": 1, + "format": "int32", + "description": "Maximum number of logs to return (1-100, defaults to 25)." + }, + "offset": { + "type": ["integer", "null"], + "title": "offset", + "minimum": 0, + "format": "int32", + "description": "Number of logs to skip for pagination (defaults to 0)." } }, - "title": "TriggerMonitorRequest", + "title": "ListMonitorHTTPResponseLogsRequest", "additionalProperties": false, - "description": "TriggerMonitorRequest is the request to trigger a monitor check." + "description": "ListMonitorHTTPResponseLogsRequest is the request to list response logs within the 14-day HTTP response-log window." }, - "openstatus.monitor.v1.TriggerMonitorResponse": { + "openstatus.monitor.v1.ListMonitorHTTPResponseLogsResponse": { "type": "object", "properties": { - "success": { - "type": "boolean", - "title": "success", - "description": "Whether the trigger was successful." + "logs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogListItem" + }, + "title": "logs", + "description": "Response logs." + }, + "pagination": { + "title": "pagination", + "description": "Pagination metadata.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPResponseLogPagination" } }, - "title": "TriggerMonitorResponse", + "title": "ListMonitorHTTPResponseLogsResponse", "additionalProperties": false, - "description": "TriggerMonitorResponse is the response after triggering a monitor." + "description": "ListMonitorHTTPResponseLogsResponse is the response containing response logs." }, - "openstatus.monitor.v1.UpdateDNSMonitorRequest": { + "openstatus.monitor.v1.ListMonitorsRequest": { "type": "object", "properties": { - "id": { - "type": "string", - "title": "id", - "minLength": 1, - "description": "Monitor ID to update (required)." + "limit": { + "type": ["integer", "null"], + "title": "limit", + "maximum": 100, + "minimum": 1, + "format": "int32", + "description": "Maximum number of monitors to return (1-100, defaults to 50)." }, - "monitor": { - "oneOf": [ - { - "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" - }, - { - "type": "null" - } - ], - "title": "monitor", - "description": "Updated monitor configuration (all fields optional for partial updates)." + "offset": { + "type": ["integer", "null"], + "title": "offset", + "minimum": 0, + "format": "int32", + "description": "Number of monitors to skip for pagination (defaults to 0)." } }, - "title": "UpdateDNSMonitorRequest", + "title": "ListMonitorsRequest", "additionalProperties": false, - "description": "UpdateDNSMonitorRequest is the request to update an existing DNS monitor." + "description": "ListMonitorsRequest is the request to list monitors." }, - "openstatus.monitor.v1.UpdateDNSMonitorResponse": { + "openstatus.monitor.v1.ListMonitorsResponse": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "The updated monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + "httpMonitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" + }, + "title": "http_monitors", + "description": "HTTP monitors in the workspace." + }, + "tcpMonitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + }, + "title": "tcp_monitors", + "description": "TCP monitors in the workspace." + }, + "dnsMonitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + }, + "title": "dns_monitors", + "description": "DNS monitors in the workspace." + }, + "icmpMonitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + }, + "title": "icmp_monitors", + "description": "ICMP monitors in the workspace." + }, + "grpcMonitors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" + }, + "title": "grpc_monitors", + "description": "gRPC monitors in the workspace." + }, + "totalSize": { + "type": "integer", + "title": "total_size", + "format": "int32", + "description": "Total number of monitors across all types." } }, - "title": "UpdateDNSMonitorResponse", + "title": "ListMonitorsResponse", "additionalProperties": false, - "description": "UpdateDNSMonitorResponse is the response after updating a DNS monitor." + "description": "ListMonitorsResponse is the response containing a list of monitors." }, - "openstatus.monitor.v1.UpdateGRPCMonitorRequest": { + "openstatus.monitor.v1.MonitorConfig": { "type": "object", - "properties": { - "id": { - "type": "string", - "title": "id", - "minLength": 1, - "description": "Monitor ID to update (required)." + "oneOf": [ + { + "type": "object", + "properties": { + "dns": { + "title": "dns", + "description": "DNS monitor configuration.", + "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + } + }, + "title": "dns", + "required": ["dns"] }, - "monitor": { - "oneOf": [ - { + { + "type": "object", + "properties": { + "grpc": { + "title": "grpc", + "description": "gRPC monitor configuration.", "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" - }, - { - "type": "null" } - ], - "title": "monitor", - "description": "Updated monitor configuration (all fields optional for partial updates)." + }, + "title": "grpc", + "required": ["grpc"] + }, + { + "type": "object", + "properties": { + "http": { + "title": "http", + "description": "HTTP monitor configuration.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" + } + }, + "title": "http", + "required": ["http"] + }, + { + "type": "object", + "properties": { + "icmp": { + "title": "icmp", + "description": "ICMP monitor configuration.", + "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + } + }, + "title": "icmp", + "required": ["icmp"] + }, + { + "type": "object", + "properties": { + "tcp": { + "title": "tcp", + "description": "TCP monitor configuration.", + "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + } + }, + "title": "tcp", + "required": ["tcp"] } - }, - "title": "UpdateGRPCMonitorRequest", + ], + "title": "MonitorConfig", "additionalProperties": false, - "description": "UpdateGRPCMonitorRequest is the request to update an existing gRPC monitor." + "description": "MonitorConfig represents the type-specific configuration for a monitor." }, - "openstatus.monitor.v1.UpdateGRPCMonitorResponse": { - "type": "object", - "properties": { - "monitor": { - "title": "monitor", - "description": "The updated monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" - } - }, - "title": "UpdateGRPCMonitorResponse", - "additionalProperties": false, - "description": "UpdateGRPCMonitorResponse is the response after updating a gRPC monitor." + "openstatus.monitor.v1.MonitorStatus": { + "type": "string", + "title": "MonitorStatus", + "enum": [ + "MONITOR_STATUS_UNSPECIFIED", + "MONITOR_STATUS_ACTIVE", + "MONITOR_STATUS_DEGRADED", + "MONITOR_STATUS_ERROR" + ], + "description": "MonitorStatus represents the operational status of a monitor." }, - "openstatus.monitor.v1.UpdateHTTPMonitorRequest": { + "openstatus.monitor.v1.NumberComparator": { + "type": "string", + "title": "NumberComparator", + "enum": [ + "NUMBER_COMPARATOR_UNSPECIFIED", + "NUMBER_COMPARATOR_EQUAL", + "NUMBER_COMPARATOR_NOT_EQUAL", + "NUMBER_COMPARATOR_GREATER_THAN", + "NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL", + "NUMBER_COMPARATOR_LESS_THAN", + "NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL" + ], + "description": "NumberComparator defines comparison operations for numeric values." + }, + "openstatus.monitor.v1.OpenTelemetryConfig": { "type": "object", "properties": { - "id": { + "endpoint": { "type": "string", - "title": "id", - "minLength": 1, - "description": "Monitor ID to update (required)." + "title": "endpoint", + "maxLength": 2048, + "description": "OTEL endpoint URL." }, - "monitor": { - "oneOf": [ - { - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" - }, - { - "type": "null" - } - ], - "title": "monitor", - "description": "Updated monitor configuration (all fields optional for partial updates)." + "headers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.monitor.v1.Headers" + }, + "title": "headers", + "maxItems": 20, + "description": "Custom headers for OTEL requests." } }, - "title": "UpdateHTTPMonitorRequest", + "title": "OpenTelemetryConfig", "additionalProperties": false, - "description": "UpdateHTTPMonitorRequest is the request to update an existing HTTP monitor." + "description": "OpenTelemetry configuration for exporting metrics." }, - "openstatus.monitor.v1.UpdateHTTPMonitorResponse": { - "type": "object", - "properties": { - "monitor": { - "title": "monitor", - "description": "The updated monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" - } - }, - "title": "UpdateHTTPMonitorResponse", - "additionalProperties": false, - "description": "UpdateHTTPMonitorResponse is the response after updating an HTTP monitor." + "openstatus.monitor.v1.Periodicity": { + "type": "string", + "title": "Periodicity", + "enum": [ + "PERIODICITY_UNSPECIFIED", + "PERIODICITY_30S", + "PERIODICITY_1M", + "PERIODICITY_5M", + "PERIODICITY_10M", + "PERIODICITY_30M", + "PERIODICITY_1H" + ], + "description": "Monitor periodicity options." }, - "openstatus.monitor.v1.UpdateICMPMonitorRequest": { + "openstatus.monitor.v1.RecordAssertion": { "type": "object", "properties": { - "id": { + "record": { "type": "string", - "title": "id", - "minLength": 1, - "description": "Monitor ID to update (required)." + "title": "record", + "enum": ["A", "AAAA", "CNAME", "MX", "TXT"], + "description": "DNS record type (e.g., \"A\", \"AAAA\", \"CNAME\", \"MX\", \"TXT\")." }, - "monitor": { - "oneOf": [ - { - "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" - }, - { - "type": "null" - } - ], - "title": "monitor", - "description": "Updated monitor configuration (all fields optional for partial updates)." + "comparator": { + "not": { + "enum": ["RECORD_COMPARATOR_UNSPECIFIED"] + }, + "title": "comparator", + "description": "Comparison operation (required, must not be UNSPECIFIED).", + "$ref": "#/components/schemas/openstatus.monitor.v1.RecordComparator" + }, + "target": { + "type": "string", + "title": "target", + "description": "Target value to compare against." } }, - "title": "UpdateICMPMonitorRequest", + "title": "RecordAssertion", "additionalProperties": false, - "description": "UpdateICMPMonitorRequest is the request to update an existing ICMP monitor." + "description": "RecordAssertion defines an assertion for DNS records." }, - "openstatus.monitor.v1.UpdateICMPMonitorResponse": { - "type": "object", - "properties": { - "monitor": { - "title": "monitor", - "description": "The updated monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" - } - }, - "title": "UpdateICMPMonitorResponse", - "additionalProperties": false, - "description": "UpdateICMPMonitorResponse is the response after updating an ICMP monitor." + "openstatus.monitor.v1.RecordComparator": { + "type": "string", + "title": "RecordComparator", + "enum": [ + "RECORD_COMPARATOR_UNSPECIFIED", + "RECORD_COMPARATOR_EQUAL", + "RECORD_COMPARATOR_NOT_EQUAL", + "RECORD_COMPARATOR_CONTAINS", + "RECORD_COMPARATOR_NOT_CONTAINS" + ], + "description": "RecordComparator defines comparison operations for DNS records." }, - "openstatus.monitor.v1.UpdateTCPMonitorRequest": { + "openstatus.monitor.v1.Region": { + "type": "string", + "title": "Region", + "enum": [ + "REGION_UNSPECIFIED", + "REGION_FLY_AMS", + "REGION_FLY_ARN", + "REGION_FLY_BOM", + "REGION_FLY_CDG", + "REGION_FLY_DFW", + "REGION_FLY_EWR", + "REGION_FLY_FRA", + "REGION_FLY_GRU", + "REGION_FLY_IAD", + "REGION_FLY_JNB", + "REGION_FLY_LAX", + "REGION_FLY_LHR", + "REGION_FLY_NRT", + "REGION_FLY_ORD", + "REGION_FLY_SJC", + "REGION_FLY_SIN", + "REGION_FLY_SYD", + "REGION_FLY_YYZ", + "REGION_KOYEB_FRA", + "REGION_KOYEB_PAR", + "REGION_KOYEB_SFO", + "REGION_KOYEB_SIN", + "REGION_KOYEB_TYO", + "REGION_KOYEB_WAS", + "REGION_RAILWAY_US_WEST2", + "REGION_RAILWAY_US_EAST4", + "REGION_RAILWAY_EUROPE_WEST4", + "REGION_RAILWAY_ASIA_SOUTHEAST1" + ], + "description": "Geographic regions where monitors can run checks from.\n REGION_FLY_BOM is deprecated and rejected on create/update; use REGION_FLY_SIN." + }, + "openstatus.monitor.v1.RegionStatus": { "type": "object", "properties": { - "id": { - "type": "string", - "title": "id", - "minLength": 1, - "description": "Monitor ID to update (required)." + "region": { + "title": "region", + "description": "The region identifier.", + "$ref": "#/components/schemas/openstatus.monitor.v1.Region" }, - "monitor": { - "oneOf": [ - { - "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" - }, - { - "type": "null" - } - ], - "title": "monitor", - "description": "Updated monitor configuration (all fields optional for partial updates)." + "status": { + "title": "status", + "description": "The status of the monitor in this region.", + "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" } }, - "title": "UpdateTCPMonitorRequest", + "title": "RegionStatus", "additionalProperties": false, - "description": "UpdateTCPMonitorRequest is the request to update an existing TCP monitor." + "description": "RegionStatus represents the status of a monitor in a specific region." }, - "openstatus.monitor.v1.UpdateTCPMonitorResponse": { + "openstatus.monitor.v1.StatusCodeAssertion": { "type": "object", "properties": { - "monitor": { - "title": "monitor", - "description": "The updated monitor.", - "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + "target": { + "type": ["integer", "string"], + "title": "target", + "maximum": 599, + "minimum": 100, + "format": "int64", + "description": "Target status code to compare against (100-599)." + }, + "comparator": { + "not": { + "enum": ["NUMBER_COMPARATOR_UNSPECIFIED"] + }, + "title": "comparator", + "description": "Comparison operation (required, must not be UNSPECIFIED).", + "$ref": "#/components/schemas/openstatus.monitor.v1.NumberComparator" } }, - "title": "UpdateTCPMonitorResponse", + "title": "StatusCodeAssertion", "additionalProperties": false, - "description": "UpdateTCPMonitorResponse is the response after updating a TCP monitor." + "description": "StatusCodeAssertion defines an assertion for HTTP status codes." }, - "openstatus.notification.v1.CheckNotificationLimitRequest": { - "type": "object", - "title": "CheckNotificationLimitRequest", - "additionalProperties": false, - "description": "CheckNotificationLimitRequest is the request to check notification limits." + "openstatus.monitor.v1.StringComparator": { + "type": "string", + "title": "StringComparator", + "enum": [ + "STRING_COMPARATOR_UNSPECIFIED", + "STRING_COMPARATOR_CONTAINS", + "STRING_COMPARATOR_NOT_CONTAINS", + "STRING_COMPARATOR_EQUAL", + "STRING_COMPARATOR_NOT_EQUAL", + "STRING_COMPARATOR_EMPTY", + "STRING_COMPARATOR_NOT_EMPTY", + "STRING_COMPARATOR_GREATER_THAN", + "STRING_COMPARATOR_GREATER_THAN_OR_EQUAL", + "STRING_COMPARATOR_LESS_THAN", + "STRING_COMPARATOR_LESS_THAN_OR_EQUAL" + ], + "description": "StringComparator defines comparison operations for string values." }, - "openstatus.notification.v1.CheckNotificationLimitResponse": { + "openstatus.monitor.v1.TCPMonitor": { "type": "object", "properties": { - "limitReached": { - "type": "boolean", - "title": "limit_reached", - "description": "Whether the workspace has reached its notification limit." - }, - "currentCount": { - "type": "integer", - "title": "current_count", - "format": "int32", - "description": "Current number of notification channels." + "id": { + "type": "string", + "title": "id", + "description": "Unique identifier for the monitor (output only for create requests)." }, - "maxCount": { - "type": "integer", - "title": "max_count", - "format": "int32", - "description": "Maximum allowed notification channels." - } - }, - "title": "CheckNotificationLimitResponse", - "additionalProperties": false, - "description": "CheckNotificationLimitResponse is the response containing limit information." - }, - "openstatus.notification.v1.CreateNotificationRequest": { - "type": "object", - "properties": { "name": { "type": "string", - "examples": ["Slack Ops Channel"], + "examples": ["Database Connection Check"], "title": "name", + "maxLength": 256, "minLength": 1, - "description": "Display name for the notification channel." + "description": "Name of the monitor (required, max 256 characters)." }, - "provider": { + "uri": { + "type": "string", + "examples": ["tcp://db.example.com:5432"], + "title": "uri", + "maxLength": 2048, + "minLength": 1, + "description": "URI to monitor in format \"host:port\" (required, max 2048 characters)." + }, + "periodicity": { "not": { - "enum": ["NOTIFICATION_PROVIDER_UNSPECIFIED"] + "enum": ["PERIODICITY_UNSPECIFIED"] }, - "title": "provider", - "description": "Provider type.", - "$ref": "#/components/schemas/openstatus.notification.v1.NotificationProvider" - }, - "data": { - "title": "data", - "description": "Provider-specific configuration.", - "$ref": "#/components/schemas/openstatus.notification.v1.NotificationData" + "title": "periodicity", + "description": "Check periodicity (required).", + "$ref": "#/components/schemas/openstatus.monitor.v1.Periodicity" }, - "monitorIds": { + "timeout": { + "type": ["integer", "string"], + "title": "timeout", + "maximum": 120000, + "minimum": 0, + "format": "int64", + "description": "Timeout in milliseconds (0-120000, defaults to 45000)." + }, + "degradedAt": { + "type": ["integer", "string", "null"], + "title": "degraded_at", + "maximum": 120000, + "minimum": 0, + "format": "int64", + "description": "Latency threshold for degraded status in milliseconds (optional, 0-120000)." + }, + "retry": { + "type": ["integer", "string"], + "title": "retry", + "maximum": 10, + "minimum": 0, + "format": "int64", + "description": "Number of retry attempts (0-10, defaults to 3)." + }, + "description": { + "type": ["string", "null"], + "title": "description", + "maxLength": 1024, + "description": "Description of the monitor (optional)." + }, + "active": { + "type": ["boolean", "null"], + "title": "active", + "description": "Whether the monitor is active (defaults to false)." + }, + "public": { + "type": ["boolean", "null"], + "title": "public", + "description": "Whether the monitor is publicly visible (defaults to false)." + }, + "regions": { "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/openstatus.monitor.v1.Region" }, - "title": "monitor_ids", - "description": "IDs of monitors to associate with this notification." + "title": "regions", + "maxItems": 28, + "description": "Geographic regions to run checks from." + }, + "openTelemetry": { + "title": "open_telemetry", + "description": "OpenTelemetry configuration for exporting metrics.", + "$ref": "#/components/schemas/openstatus.monitor.v1.OpenTelemetryConfig" + }, + "status": { + "title": "status", + "description": "Current operational status of the monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.MonitorStatus" + }, + "privateLocationIds": { + "type": "array", + "items": { + "type": "string", + "readOnly": true + }, + "title": "private_location_ids", + "description": "IDs of private locations that run this monitor. Read-only.", + "readOnly": true } }, - "title": "CreateNotificationRequest", - "required": ["data"], + "title": "TCPMonitor", "additionalProperties": false, - "description": "CreateNotificationRequest is the request to create a new notification channel." + "description": "TCPMonitor defines the configuration for a TCP monitor." }, - "openstatus.notification.v1.CreateNotificationResponse": { - "type": "object", - "properties": { - "notification": { - "title": "notification", - "description": "The created notification channel.", - "$ref": "#/components/schemas/openstatus.notification.v1.Notification" - } - }, - "title": "CreateNotificationResponse", - "additionalProperties": false, - "description": "CreateNotificationResponse is the response after creating a notification channel." + "openstatus.monitor.v1.TimeRange": { + "type": "string", + "title": "TimeRange", + "enum": [ + "TIME_RANGE_UNSPECIFIED", + "TIME_RANGE_1D", + "TIME_RANGE_7D", + "TIME_RANGE_14D" + ], + "description": "TimeRange represents the time period for metrics aggregation." }, - "openstatus.notification.v1.DeleteNotificationRequest": { + "openstatus.monitor.v1.TriggerMonitorRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", "minLength": 1, - "description": "Notification ID to delete (required)." + "description": "Monitor ID to trigger (required)." } }, - "title": "DeleteNotificationRequest", + "title": "TriggerMonitorRequest", "additionalProperties": false, - "description": "DeleteNotificationRequest is the request to delete a notification channel." + "description": "TriggerMonitorRequest is the request to trigger a monitor check." }, - "openstatus.notification.v1.DeleteNotificationResponse": { + "openstatus.monitor.v1.TriggerMonitorResponse": { "type": "object", "properties": { "success": { "type": "boolean", "title": "success", - "description": "Whether the deletion was successful." + "description": "Whether the trigger was successful." } }, - "title": "DeleteNotificationResponse", + "title": "TriggerMonitorResponse", "additionalProperties": false, - "description": "DeleteNotificationResponse is the response after deleting a notification channel." + "description": "TriggerMonitorResponse is the response after triggering a monitor." }, - "openstatus.notification.v1.DiscordData": { + "openstatus.monitor.v1.UpdateDNSMonitorRequest": { "type": "object", "properties": { - "webhookUrl": { + "id": { "type": "string", - "examples": ["https://discord.com/api/webhooks/123/abc"], - "title": "webhook_url", - "format": "uri", - "description": "Discord webhook URL." + "title": "id", + "minLength": 1, + "description": "Monitor ID to update (required)." + }, + "monitor": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" + }, + { + "type": "null" + } + ], + "title": "monitor", + "description": "Updated monitor configuration (all fields optional for partial updates)." } }, - "title": "DiscordData", + "title": "UpdateDNSMonitorRequest", "additionalProperties": false, - "description": "DiscordData contains configuration for Discord notifications." + "description": "UpdateDNSMonitorRequest is the request to update an existing DNS monitor." }, - "openstatus.notification.v1.EmailData": { + "openstatus.monitor.v1.UpdateDNSMonitorResponse": { "type": "object", "properties": { - "email": { - "type": "string", - "examples": ["ops-team@example.com"], - "title": "email", - "format": "email", - "description": "Email address to send notifications to." + "monitor": { + "title": "monitor", + "description": "The updated monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.DNSMonitor" } }, - "title": "EmailData", + "title": "UpdateDNSMonitorResponse", "additionalProperties": false, - "description": "EmailData contains configuration for email notifications." + "description": "UpdateDNSMonitorResponse is the response after updating a DNS monitor." }, - "openstatus.notification.v1.GetNotificationRequest": { + "openstatus.monitor.v1.UpdateGRPCMonitorRequest": { "type": "object", "properties": { "id": { "type": "string", "title": "id", "minLength": 1, - "description": "Notification ID to retrieve (required)." + "description": "Monitor ID to update (required)." + }, + "monitor": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" + }, + { + "type": "null" + } + ], + "title": "monitor", + "description": "Updated monitor configuration (all fields optional for partial updates)." } }, - "title": "GetNotificationRequest", + "title": "UpdateGRPCMonitorRequest", "additionalProperties": false, - "description": "GetNotificationRequest is the request to get a notification channel." + "description": "UpdateGRPCMonitorRequest is the request to update an existing gRPC monitor." }, - "openstatus.notification.v1.GetNotificationResponse": { + "openstatus.monitor.v1.UpdateGRPCMonitorResponse": { "type": "object", "properties": { - "notification": { - "title": "notification", - "description": "The notification channel.", - "$ref": "#/components/schemas/openstatus.notification.v1.Notification" + "monitor": { + "title": "monitor", + "description": "The updated monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.GRPCMonitor" } }, - "title": "GetNotificationResponse", + "title": "UpdateGRPCMonitorResponse", "additionalProperties": false, - "description": "GetNotificationResponse is the response containing the notification channel." + "description": "UpdateGRPCMonitorResponse is the response after updating a gRPC monitor." }, - "openstatus.notification.v1.GoogleChatData": { + "openstatus.monitor.v1.UpdateHTTPMonitorRequest": { "type": "object", "properties": { - "webhookUrl": { + "id": { "type": "string", - "title": "webhook_url", - "format": "uri", - "description": "Google Chat webhook URL." + "title": "id", + "minLength": 1, + "description": "Monitor ID to update (required)." + }, + "monitor": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" + }, + { + "type": "null" + } + ], + "title": "monitor", + "description": "Updated monitor configuration (all fields optional for partial updates)." } }, - "title": "GoogleChatData", + "title": "UpdateHTTPMonitorRequest", "additionalProperties": false, - "description": "GoogleChatData contains configuration for Google Chat notifications." + "description": "UpdateHTTPMonitorRequest is the request to update an existing HTTP monitor." }, - "openstatus.notification.v1.GrafanaOncallData": { + "openstatus.monitor.v1.UpdateHTTPMonitorResponse": { "type": "object", "properties": { - "webhookUrl": { - "type": "string", - "title": "webhook_url", - "format": "uri", - "description": "Grafana OnCall webhook URL." + "monitor": { + "title": "monitor", + "description": "The updated monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.HTTPMonitor" } }, - "title": "GrafanaOncallData", + "title": "UpdateHTTPMonitorResponse", "additionalProperties": false, - "description": "GrafanaOncallData contains configuration for Grafana OnCall notifications." + "description": "UpdateHTTPMonitorResponse is the response after updating an HTTP monitor." }, - "openstatus.notification.v1.ListNotificationsRequest": { + "openstatus.monitor.v1.UpdateICMPMonitorRequest": { "type": "object", "properties": { - "limit": { - "type": ["integer", "null"], - "title": "limit", - "maximum": 100, - "minimum": 1, - "format": "int32", - "description": "Maximum number of notifications to return (1-100, defaults to 50)." + "id": { + "type": "string", + "title": "id", + "minLength": 1, + "description": "Monitor ID to update (required)." }, - "offset": { - "type": ["integer", "null"], - "title": "offset", - "minimum": 0, - "format": "int32", - "description": "Number of notifications to skip for pagination (defaults to 0)." - } - }, - "title": "ListNotificationsRequest", - "additionalProperties": false, - "description": "ListNotificationsRequest is the request to list notification channels." - }, - "openstatus.notification.v1.ListNotificationsResponse": { + "monitor": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" + }, + { + "type": "null" + } + ], + "title": "monitor", + "description": "Updated monitor configuration (all fields optional for partial updates)." + } + }, + "title": "UpdateICMPMonitorRequest", + "additionalProperties": false, + "description": "UpdateICMPMonitorRequest is the request to update an existing ICMP monitor." + }, + "openstatus.monitor.v1.UpdateICMPMonitorResponse": { "type": "object", "properties": { - "notifications": { - "type": "array", - "items": { - "$ref": "#/components/schemas/openstatus.notification.v1.NotificationSummary" - }, - "title": "notifications", - "description": "Notification channel summaries." - }, - "totalSize": { - "type": "integer", - "title": "total_size", - "format": "int32", - "description": "Total number of notification channels." + "monitor": { + "title": "monitor", + "description": "The updated monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.ICMPMonitor" } }, - "title": "ListNotificationsResponse", + "title": "UpdateICMPMonitorResponse", "additionalProperties": false, - "description": "ListNotificationsResponse is the response containing notification channels." + "description": "UpdateICMPMonitorResponse is the response after updating an ICMP monitor." }, - "openstatus.notification.v1.MsTeamsData": { + "openstatus.monitor.v1.UpdateTCPMonitorRequest": { "type": "object", "properties": { - "webhookUrl": { + "id": { "type": "string", - "examples": [ - "https://prod-00.westeurope.logic.azure.com:443/workflows/abc/triggers/manual/paths/invoke" + "title": "id", + "minLength": 1, + "description": "Monitor ID to update (required)." + }, + "monitor": { + "oneOf": [ + { + "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + }, + { + "type": "null" + } ], - "title": "webhook_url", - "format": "uri", - "description": "Microsoft Teams webhook URL (Power Automate Workflows)." + "title": "monitor", + "description": "Updated monitor configuration (all fields optional for partial updates)." } }, - "title": "MsTeamsData", + "title": "UpdateTCPMonitorRequest", "additionalProperties": false, - "description": "MsTeamsData contains configuration for Microsoft Teams notifications." + "description": "UpdateTCPMonitorRequest is the request to update an existing TCP monitor." }, - "openstatus.notification.v1.Notification": { + "openstatus.monitor.v1.UpdateTCPMonitorResponse": { "type": "object", "properties": { - "id": { - "type": "string", - "title": "id", - "description": "Unique identifier for the notification." + "monitor": { + "title": "monitor", + "description": "The updated monitor.", + "$ref": "#/components/schemas/openstatus.monitor.v1.TCPMonitor" + } + }, + "title": "UpdateTCPMonitorResponse", + "additionalProperties": false, + "description": "UpdateTCPMonitorResponse is the response after updating a TCP monitor." + }, + "openstatus.notification.v1.CheckNotificationLimitRequest": { + "type": "object", + "title": "CheckNotificationLimitRequest", + "additionalProperties": false, + "description": "CheckNotificationLimitRequest is the request to check notification limits." + }, + "openstatus.notification.v1.CheckNotificationLimitResponse": { + "type": "object", + "properties": { + "limitReached": { + "type": "boolean", + "title": "limit_reached", + "description": "Whether the workspace has reached its notification limit." }, + "currentCount": { + "type": "integer", + "title": "current_count", + "format": "int32", + "description": "Current number of notification channels." + }, + "maxCount": { + "type": "integer", + "title": "max_count", + "format": "int32", + "description": "Maximum allowed notification channels." + } + }, + "title": "CheckNotificationLimitResponse", + "additionalProperties": false, + "description": "CheckNotificationLimitResponse is the response containing limit information." + }, + "openstatus.notification.v1.CreateNotificationRequest": { + "type": "object", + "properties": { "name": { "type": "string", + "examples": ["Slack Ops Channel"], "title": "name", + "minLength": 1, "description": "Display name for the notification channel." }, "provider": { + "not": { + "enum": ["NOTIFICATION_PROVIDER_UNSPECIFIED"] + }, "title": "provider", "description": "Provider type.", "$ref": "#/components/schemas/openstatus.notification.v1.NotificationProvider" @@ -2734,87 +3472,311 @@ "type": "string" }, "title": "monitor_ids", - "description": "IDs of monitors associated with this notification." - }, - "createdAt": { + "description": "IDs of monitors to associate with this notification." + } + }, + "title": "CreateNotificationRequest", + "required": ["data"], + "additionalProperties": false, + "description": "CreateNotificationRequest is the request to create a new notification channel." + }, + "openstatus.notification.v1.CreateNotificationResponse": { + "type": "object", + "properties": { + "notification": { + "title": "notification", + "description": "The created notification channel.", + "$ref": "#/components/schemas/openstatus.notification.v1.Notification" + } + }, + "title": "CreateNotificationResponse", + "additionalProperties": false, + "description": "CreateNotificationResponse is the response after creating a notification channel." + }, + "openstatus.notification.v1.DeleteNotificationRequest": { + "type": "object", + "properties": { + "id": { "type": "string", - "title": "created_at", - "description": "Timestamp when the notification was created (RFC 3339)." - }, - "updatedAt": { + "title": "id", + "minLength": 1, + "description": "Notification ID to delete (required)." + } + }, + "title": "DeleteNotificationRequest", + "additionalProperties": false, + "description": "DeleteNotificationRequest is the request to delete a notification channel." + }, + "openstatus.notification.v1.DeleteNotificationResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "title": "success", + "description": "Whether the deletion was successful." + } + }, + "title": "DeleteNotificationResponse", + "additionalProperties": false, + "description": "DeleteNotificationResponse is the response after deleting a notification channel." + }, + "openstatus.notification.v1.DiscordData": { + "type": "object", + "properties": { + "webhookUrl": { "type": "string", - "title": "updated_at", - "description": "Timestamp when the notification was last updated (RFC 3339)." + "examples": ["https://discord.com/api/webhooks/123/abc"], + "title": "webhook_url", + "format": "uri", + "description": "Discord webhook URL." } }, - "title": "Notification", + "title": "DiscordData", "additionalProperties": false, - "description": "Notification represents a notification channel with full details." + "description": "DiscordData contains configuration for Discord notifications." }, - "openstatus.notification.v1.NotificationData": { + "openstatus.notification.v1.EmailData": { "type": "object", - "oneOf": [ - { - "type": "object", - "properties": { - "discord": { - "title": "discord", - "description": "Discord configuration.", - "$ref": "#/components/schemas/openstatus.notification.v1.DiscordData" - } - }, - "title": "discord", - "required": ["discord"] - }, - { - "type": "object", - "properties": { - "email": { - "title": "email", - "description": "Email configuration.", - "$ref": "#/components/schemas/openstatus.notification.v1.EmailData" - } - }, + "properties": { + "email": { + "type": "string", + "examples": ["ops-team@example.com"], "title": "email", - "required": ["email"] - }, - { - "type": "object", - "properties": { - "googleChat": { - "title": "google_chat", - "description": "Google Chat configuration.", - "$ref": "#/components/schemas/openstatus.notification.v1.GoogleChatData" - } - }, - "title": "google_chat", - "required": ["googleChat"] - }, - { - "type": "object", - "properties": { - "grafanaOncall": { - "title": "grafana_oncall", - "description": "Grafana OnCall configuration.", - "$ref": "#/components/schemas/openstatus.notification.v1.GrafanaOncallData" - } - }, - "title": "grafana_oncall", - "required": ["grafanaOncall"] - }, - { - "type": "object", - "properties": { - "msTeams": { - "title": "ms_teams", - "description": "Microsoft Teams configuration.", - "$ref": "#/components/schemas/openstatus.notification.v1.MsTeamsData" - } - }, - "title": "ms_teams", - "required": ["msTeams"] - }, - { + "format": "email", + "description": "Email address to send notifications to." + } + }, + "title": "EmailData", + "additionalProperties": false, + "description": "EmailData contains configuration for email notifications." + }, + "openstatus.notification.v1.GetNotificationRequest": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "id", + "minLength": 1, + "description": "Notification ID to retrieve (required)." + } + }, + "title": "GetNotificationRequest", + "additionalProperties": false, + "description": "GetNotificationRequest is the request to get a notification channel." + }, + "openstatus.notification.v1.GetNotificationResponse": { + "type": "object", + "properties": { + "notification": { + "title": "notification", + "description": "The notification channel.", + "$ref": "#/components/schemas/openstatus.notification.v1.Notification" + } + }, + "title": "GetNotificationResponse", + "additionalProperties": false, + "description": "GetNotificationResponse is the response containing the notification channel." + }, + "openstatus.notification.v1.GoogleChatData": { + "type": "object", + "properties": { + "webhookUrl": { + "type": "string", + "title": "webhook_url", + "format": "uri", + "description": "Google Chat webhook URL." + } + }, + "title": "GoogleChatData", + "additionalProperties": false, + "description": "GoogleChatData contains configuration for Google Chat notifications." + }, + "openstatus.notification.v1.GrafanaOncallData": { + "type": "object", + "properties": { + "webhookUrl": { + "type": "string", + "title": "webhook_url", + "format": "uri", + "description": "Grafana OnCall webhook URL." + } + }, + "title": "GrafanaOncallData", + "additionalProperties": false, + "description": "GrafanaOncallData contains configuration for Grafana OnCall notifications." + }, + "openstatus.notification.v1.ListNotificationsRequest": { + "type": "object", + "properties": { + "limit": { + "type": ["integer", "null"], + "title": "limit", + "maximum": 100, + "minimum": 1, + "format": "int32", + "description": "Maximum number of notifications to return (1-100, defaults to 50)." + }, + "offset": { + "type": ["integer", "null"], + "title": "offset", + "minimum": 0, + "format": "int32", + "description": "Number of notifications to skip for pagination (defaults to 0)." + } + }, + "title": "ListNotificationsRequest", + "additionalProperties": false, + "description": "ListNotificationsRequest is the request to list notification channels." + }, + "openstatus.notification.v1.ListNotificationsResponse": { + "type": "object", + "properties": { + "notifications": { + "type": "array", + "items": { + "$ref": "#/components/schemas/openstatus.notification.v1.NotificationSummary" + }, + "title": "notifications", + "description": "Notification channel summaries." + }, + "totalSize": { + "type": "integer", + "title": "total_size", + "format": "int32", + "description": "Total number of notification channels." + } + }, + "title": "ListNotificationsResponse", + "additionalProperties": false, + "description": "ListNotificationsResponse is the response containing notification channels." + }, + "openstatus.notification.v1.MsTeamsData": { + "type": "object", + "properties": { + "webhookUrl": { + "type": "string", + "examples": [ + "https://prod-00.westeurope.logic.azure.com:443/workflows/abc/triggers/manual/paths/invoke" + ], + "title": "webhook_url", + "format": "uri", + "description": "Microsoft Teams webhook URL (Power Automate Workflows)." + } + }, + "title": "MsTeamsData", + "additionalProperties": false, + "description": "MsTeamsData contains configuration for Microsoft Teams notifications." + }, + "openstatus.notification.v1.Notification": { + "type": "object", + "properties": { + "id": { + "type": "string", + "title": "id", + "description": "Unique identifier for the notification." + }, + "name": { + "type": "string", + "title": "name", + "description": "Display name for the notification channel." + }, + "provider": { + "title": "provider", + "description": "Provider type.", + "$ref": "#/components/schemas/openstatus.notification.v1.NotificationProvider" + }, + "data": { + "title": "data", + "description": "Provider-specific configuration.", + "$ref": "#/components/schemas/openstatus.notification.v1.NotificationData" + }, + "monitorIds": { + "type": "array", + "items": { + "type": "string" + }, + "title": "monitor_ids", + "description": "IDs of monitors associated with this notification." + }, + "createdAt": { + "type": "string", + "title": "created_at", + "description": "Timestamp when the notification was created (RFC 3339)." + }, + "updatedAt": { + "type": "string", + "title": "updated_at", + "description": "Timestamp when the notification was last updated (RFC 3339)." + } + }, + "title": "Notification", + "additionalProperties": false, + "description": "Notification represents a notification channel with full details." + }, + "openstatus.notification.v1.NotificationData": { + "type": "object", + "oneOf": [ + { + "type": "object", + "properties": { + "discord": { + "title": "discord", + "description": "Discord configuration.", + "$ref": "#/components/schemas/openstatus.notification.v1.DiscordData" + } + }, + "title": "discord", + "required": ["discord"] + }, + { + "type": "object", + "properties": { + "email": { + "title": "email", + "description": "Email configuration.", + "$ref": "#/components/schemas/openstatus.notification.v1.EmailData" + } + }, + "title": "email", + "required": ["email"] + }, + { + "type": "object", + "properties": { + "googleChat": { + "title": "google_chat", + "description": "Google Chat configuration.", + "$ref": "#/components/schemas/openstatus.notification.v1.GoogleChatData" + } + }, + "title": "google_chat", + "required": ["googleChat"] + }, + { + "type": "object", + "properties": { + "grafanaOncall": { + "title": "grafana_oncall", + "description": "Grafana OnCall configuration.", + "$ref": "#/components/schemas/openstatus.notification.v1.GrafanaOncallData" + } + }, + "title": "grafana_oncall", + "required": ["grafanaOncall"] + }, + { + "type": "object", + "properties": { + "msTeams": { + "title": "ms_teams", + "description": "Microsoft Teams configuration.", + "$ref": "#/components/schemas/openstatus.notification.v1.MsTeamsData" + } + }, + "title": "ms_teams", + "required": ["msTeams"] + }, + { "type": "object", "properties": { "ntfy": { @@ -5778,6 +6740,12 @@ }, "title": "component_impacts", "description": "Per-component impacts set by the initial update (optional). When provided,\n the named components are added to the report's affected set. Omitting this\n field creates a legacy report without impact tracking." + }, + "incidentId": { + "type": ["string", "null"], + "title": "incident_id", + "minLength": 1, + "description": "ID of an incident to link the report to (optional). Linked in the same\n step: a closed incident, or one already linked to a report, fails the create." } }, "title": "CreateStatusReportRequest", @@ -5959,6 +6927,11 @@ "type": "string", "title": "updated_at", "description": "Timestamp when the report was last updated (RFC 3339 format)." + }, + "incidentId": { + "type": ["string", "null"], + "title": "incident_id", + "description": "ID of the incident this report communicates (unset when not linked)." } }, "title": "StatusReport", @@ -6012,6 +6985,11 @@ "type": "string", "title": "updated_at", "description": "Timestamp when the report was last updated (RFC 3339 format)." + }, + "incidentId": { + "type": ["string", "null"], + "title": "incident_id", + "description": "ID of the incident this report communicates (unset when not linked)." } }, "title": "StatusReportSummary", @@ -6081,73 +7059,596 @@ "title": "page_component_ids", "description": "New list of page component IDs (optional, replaces existing list)." }, - "updatePageComponentIds": { - "type": ["boolean", "null"], - "title": "update_page_component_ids", - "description": "Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved." + "updatePageComponentIds": { + "type": ["boolean", "null"], + "title": "update_page_component_ids", + "description": "Set to true to update page component associations.\n When true, page_component_ids replaces the existing list (empty clears all).\n When false or unset, page_component_ids is ignored and existing associations are preserved." + } + }, + "title": "UpdateStatusReportRequest", + "additionalProperties": false, + "description": "UpdateStatusReportRequest is the request to update a status report's metadata." + }, + "openstatus.status_report.v1.UpdateStatusReportResponse": { + "type": "object", + "properties": { + "statusReport": { + "title": "status_report", + "description": "The updated status report.", + "$ref": "#/components/schemas/openstatus.status_report.v1.StatusReport" + } + }, + "title": "UpdateStatusReportResponse", + "additionalProperties": false, + "description": "UpdateStatusReportResponse is the response after updating a status report." + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + { + "name": "MonitorService", + "description": "Create, update, delete, and query monitors. Supports HTTP, TCP, and DNS monitor types\nwith configurable check intervals, regions, assertions, and alerting thresholds.\n" + }, + { + "name": "StatusPageService", + "description": "Manage public status pages with components, component groups, and email subscribers.\nIncludes endpoints for retrieving full page content and aggregated status.\n" + }, + { + "name": "NotificationService", + "description": "Configure notification channels (Slack, Discord, PagerDuty, email, webhooks, etc.)\nand associate them with monitors. Supports 12 notification providers.\n" + }, + { + "name": "StatusReportService", + "description": "Create and manage public status reports with status updates. Reports follow a lifecycle:\ninvestigating -> identified -> monitoring -> resolved.\n" + }, + { + "name": "IncidentService", + "description": "Declare and run incidents: lifecycle (open -> mitigated -> resolved, or canceled),\ntimeline notes, a linked status report, and the postmortem.\n" + }, + { + "name": "MaintenanceService", + "description": "Schedule maintenance windows for status page components. Subscribers can be\nnotified automatically when maintenance is created.\n" + }, + { + "name": "HealthService", + "description": "Health check endpoint for load balancer probes. No authentication required." + }, + { + "name": "PrivateLocationService", + "description": "PrivateLocationService provides CRUD operations for private locations —\n self-hosted checker agents that run monitors from your own network." + } + ], + "paths": { + "/rpc/openstatus.health.v1.HealthService/Check": { + "get": { + "tags": ["HealthService"], + "summary": "Check", + "description": "Check returns the current serving status of the service.", + "operationId": "HealthService_Check.get", + "parameters": [ + { + "name": "message", + "in": "query", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.health.v1.CheckRequest" + } + } + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.health.v1.CheckResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + }, + "post": { + "tags": ["HealthService"], + "summary": "Check", + "description": "Check returns the current serving status of the service.", + "operationId": "HealthService_Check", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.health.v1.CheckRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.health.v1.CheckResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/AddIncidentNote": { + "post": { + "tags": ["IncidentService"], + "summary": "AddIncidentNote", + "description": "AddIncidentNote appends a note to the incident timeline.", + "operationId": "IncidentService_AddIncidentNote", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.AddIncidentNoteRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.AddIncidentNoteResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/ApprovePostmortem": { + "post": { + "tags": ["IncidentService"], + "summary": "ApprovePostmortem", + "description": "Approves the draft postmortem of a resolved incident. With close set to true the incident is closed in the same step. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this.", + "operationId": "IncidentService_ApprovePostmortem", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.ApprovePostmortemRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.ApprovePostmortemResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/CloseIncident": { + "post": { + "tags": ["IncidentService"], + "summary": "CloseIncident", + "description": "Closes a resolved incident, after which only its postmortem can change. Requires an approved postmortem, or skip_postmortem set to true. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this.", + "operationId": "IncidentService_CloseIncident", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.CloseIncidentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.CloseIncidentResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/DeclareIncident": { + "post": { + "tags": ["IncidentService"], + "summary": "DeclareIncident", + "description": "Declares a new incident in the open status. The commander is set by email and must be a member of the workspace; without one the incident is unassigned. A newly assigned commander is emailed unless they own the API key. When open_slack_channel is true and Slack is connected, a dedicated channel is created and the team invited. started_at defaults to now and can be set in the past for a retroactive declare.", + "operationId": "IncidentService_DeclareIncident", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.DeclareIncidentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.DeclareIncidentResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/DeleteIncident": { + "post": { + "tags": ["IncidentService"], + "summary": "DeleteIncident", + "description": "Deletes an incident declared by mistake. Only allowed while the incident is open and was never mitigated, resolved or closed (Incident.deletable); cancel it otherwise. Allowed for workspace owners and admins only. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this.", + "operationId": "IncidentService_DeleteIncident", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.DeleteIncidentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.DeleteIncidentResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/GetIncident": { + "get": { + "tags": ["IncidentService"], + "summary": "GetIncident", + "description": "GetIncident retrieves an incident by ID, including its timeline.", + "operationId": "IncidentService_GetIncident.get", + "parameters": [ + { + "name": "message", + "in": "query", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetIncidentRequest" + } + } + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetIncidentResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + }, + "post": { + "tags": ["IncidentService"], + "summary": "GetIncident", + "description": "GetIncident retrieves an incident by ID, including its timeline.", + "operationId": "IncidentService_GetIncident", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetIncidentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetIncidentResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/GetPostmortem": { + "get": { + "tags": ["IncidentService"], + "summary": "GetPostmortem", + "description": "GetPostmortem retrieves the postmortem of an incident, if any.", + "operationId": "IncidentService_GetPostmortem.get", + "parameters": [ + { + "name": "message", + "in": "query", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetPostmortemRequest" + } + } + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetPostmortemResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + }, + "post": { + "tags": ["IncidentService"], + "summary": "GetPostmortem", + "description": "GetPostmortem retrieves the postmortem of an incident, if any.", + "operationId": "IncidentService_GetPostmortem", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetPostmortemRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.GetPostmortemResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/LinkStatusReport": { + "post": { + "tags": ["IncidentService"], + "summary": "LinkStatusReport", + "description": "LinkStatusReport links a public status report to the incident.", + "operationId": "IncidentService_LinkStatusReport", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.LinkStatusReportRequest" + } + } + }, + "required": true }, - "title": "UpdateStatusReportRequest", - "additionalProperties": false, - "description": "UpdateStatusReportRequest is the request to update a status report's metadata." - }, - "openstatus.status_report.v1.UpdateStatusReportResponse": { - "type": "object", - "properties": { - "statusReport": { - "title": "status_report", - "description": "The updated status report.", - "$ref": "#/components/schemas/openstatus.status_report.v1.StatusReport" + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.LinkStatusReportResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } } - }, - "title": "UpdateStatusReportResponse", - "additionalProperties": false, - "description": "UpdateStatusReportResponse is the response after updating a status report." + } } - } - }, - "security": [ - { - "ApiKeyAuth": [] - } - ], - "tags": [ - { - "name": "MonitorService", - "description": "Create, update, delete, and query monitors. Supports HTTP, TCP, and DNS monitor types\nwith configurable check intervals, regions, assertions, and alerting thresholds.\n" - }, - { - "name": "StatusPageService", - "description": "Manage public status pages with components, component groups, and email subscribers.\nIncludes endpoints for retrieving full page content and aggregated status.\n" - }, - { - "name": "NotificationService", - "description": "Configure notification channels (Slack, Discord, PagerDuty, email, webhooks, etc.)\nand associate them with monitors. Supports 12 notification providers.\n" - }, - { - "name": "StatusReportService", - "description": "Create and manage incident reports with status updates. Reports follow a lifecycle:\ninvestigating -> identified -> monitoring -> resolved.\n" - }, - { - "name": "MaintenanceService", - "description": "Schedule maintenance windows for status page components. Subscribers can be\nnotified automatically when maintenance is created.\n" - }, - { - "name": "HealthService", - "description": "Health check endpoint for load balancer probes. No authentication required." }, - { - "name": "PrivateLocationService", - "description": "PrivateLocationService provides CRUD operations for private locations —\n self-hosted checker agents that run monitors from your own network." - } - ], - "paths": { - "/rpc/openstatus.health.v1.HealthService/Check": { + "/rpc/openstatus.incident.v1.IncidentService/ListIncidents": { "get": { - "tags": ["HealthService"], - "summary": "Check", - "description": "Check returns the current serving status of the service.", - "operationId": "HealthService_Check.get", + "tags": ["IncidentService"], + "summary": "ListIncidents", + "description": "ListIncidents returns the incidents of the workspace (metadata only), open first.", + "operationId": "IncidentService_ListIncidents.get", "parameters": [ { "name": "message", @@ -6155,7 +7656,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/openstatus.health.v1.CheckRequest" + "$ref": "#/components/schemas/openstatus.incident.v1.ListIncidentsRequest" } } } @@ -6167,7 +7668,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/openstatus.health.v1.CheckResponse" + "$ref": "#/components/schemas/openstatus.incident.v1.ListIncidentsResponse" } } } @@ -6188,15 +7689,15 @@ } }, "post": { - "tags": ["HealthService"], - "summary": "Check", - "description": "Check returns the current serving status of the service.", - "operationId": "HealthService_Check", + "tags": ["IncidentService"], + "summary": "ListIncidents", + "description": "ListIncidents returns the incidents of the workspace (metadata only), open first.", + "operationId": "IncidentService_ListIncidents", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/openstatus.health.v1.CheckRequest" + "$ref": "#/components/schemas/openstatus.incident.v1.ListIncidentsRequest" } } }, @@ -6208,7 +7709,179 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/openstatus.health.v1.CheckResponse" + "$ref": "#/components/schemas/openstatus.incident.v1.ListIncidentsResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/SetIncidentStatus": { + "post": { + "tags": ["IncidentService"], + "summary": "SetIncidentStatus", + "description": "Moves an incident to a new status. Allowed transitions: open -> mitigated, resolved or canceled; mitigated -> resolved, open or canceled; resolved -> open. Canceled is terminal and closes the incident. The same status, a forbidden transition or a closed incident fail with failed_precondition; Incident.allowed_transitions lists what is valid. A linked status report is never updated: post the public update with StatusReportService.AddStatusReportUpdate.", + "operationId": "IncidentService_SetIncidentStatus", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.SetIncidentStatusRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.SetIncidentStatusResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/UnlinkStatusReport": { + "post": { + "tags": ["IncidentService"], + "summary": "UnlinkStatusReport", + "description": "UnlinkStatusReport removes the link to the incident's status report.", + "operationId": "IncidentService_UnlinkStatusReport", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.UnlinkStatusReportRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.UnlinkStatusReportResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/UpdateIncident": { + "post": { + "tags": ["IncidentService"], + "summary": "UpdateIncident", + "description": "UpdateIncident edits the title, severity, summary, commander or start time of an incident.", + "operationId": "IncidentService_UpdateIncident", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.UpdateIncidentRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.UpdateIncidentResponse" + } + } + } + }, + "429": { + "$ref": "#/components/responses/RateLimited" + }, + "default": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/connect.error" + } + } + } + } + } + } + }, + "/rpc/openstatus.incident.v1.IncidentService/UpdatePostmortem": { + "post": { + "tags": ["IncidentService"], + "summary": "UpdatePostmortem", + "description": "UpdatePostmortem creates or replaces the postmortem body of a resolved incident.", + "operationId": "IncidentService_UpdatePostmortem", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.UpdatePostmortemRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/openstatus.incident.v1.UpdatePostmortemResponse" } } } diff --git a/apps/server/static/openapi.yaml b/apps/server/static/openapi.yaml index 6ae80571..db8a09e0 100644 --- a/apps/server/static/openapi.yaml +++ b/apps/server/static/openapi.yaml @@ -118,250 +118,704 @@ components: - SERVING_STATUS_SERVING - SERVING_STATUS_NOT_SERVING description: ServingStatus represents the health status of the service. - openstatus.maintenance.v1.CreateMaintenanceRequest: + openstatus.incident.v1.AddIncidentNoteRequest: type: object properties: - title: + id: type: string - examples: - - Database Migration - title: title - maxLength: 256 + title: id minLength: 1 - description: Title of the maintenance (required, 1-256 characters). + description: ID of the incident (required). message: type: string title: message + maxLength: 10000 minLength: 1 - description: Message describing the maintenance (required). - from: + description: Note text, markdown (required, 1-10000 characters). + title: AddIncidentNoteRequest + additionalProperties: false + description: AddIncidentNoteRequest is the request to add a note to an incident timeline. + openstatus.incident.v1.AddIncidentNoteResponse: + type: object + properties: + event: + title: event + description: The timeline event that was added. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentEvent' + title: AddIncidentNoteResponse + additionalProperties: false + description: AddIncidentNoteResponse is the response after adding a note. + openstatus.incident.v1.ApprovePostmortemRequest: + type: object + properties: + incidentId: type: string - examples: - - "2024-03-01T02:00:00Z" - title: from - pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: Start time of the maintenance window (RFC 3339 format, required). - to: + title: incident_id + minLength: 1 + description: ID of the incident (required). + close: + type: + - boolean + - "null" + title: close + description: Also close the incident (optional, defaults to false). + title: ApprovePostmortemRequest + additionalProperties: false + description: ApprovePostmortemRequest is the request to approve the postmortem of an incident. + openstatus.incident.v1.ApprovePostmortemResponse: + type: object + properties: + postmortem: + title: postmortem + description: The approved postmortem. + $ref: '#/components/schemas/openstatus.incident.v1.Postmortem' + incident: + title: incident + description: The incident after approval (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: ApprovePostmortemResponse + additionalProperties: false + description: ApprovePostmortemResponse is the response after approving the postmortem. + openstatus.incident.v1.CloseIncidentRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident (required). + skipPostmortem: + type: + - boolean + - "null" + title: skip_postmortem + description: Close without an approved postmortem (optional, defaults to false). + title: CloseIncidentRequest + additionalProperties: false + description: CloseIncidentRequest is the request to close a resolved incident. + openstatus.incident.v1.CloseIncidentResponse: + type: object + properties: + incident: + title: incident + description: The closed incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: CloseIncidentResponse + additionalProperties: false + description: CloseIncidentResponse is the response after closing an incident. + openstatus.incident.v1.DeclareIncidentRequest: + type: object + properties: + title: type: string examples: - - "2024-03-01T06:00:00Z" - title: to + - Checkout API returns 502 + title: title + maxLength: 256 + minLength: 1 + description: Title of the incident (required, 1-256 characters). + severity: + not: + enum: + - INCIDENT_SEVERITY_UNSPECIFIED + title: severity + description: Severity of the incident (required). + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + summary: + type: + - string + - "null" + title: summary + maxLength: 4000 + description: Short human summary (optional, up to 4000 characters). + commanderEmail: + type: + - string + - "null" + examples: + - jane@example.com + title: commander_email + format: email + description: Email of the member who leads the response (optional, defaults to unassigned). + startedAt: + type: + - string + - "null" + examples: + - "2024-03-15T10:30:00Z" + title: started_at pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: End time of the maintenance window (RFC 3339 format, required). - pageId: - type: string - title: page_id + description: When the impact began (RFC 3339 format, optional, defaults to now). + statusReportId: + type: + - string + - "null" + title: status_report_id minLength: 1 - description: Page ID to associate with this maintenance (required). - pageComponentIds: - type: array - items: - type: string - title: page_component_ids - description: Page component IDs to associate with this maintenance (optional). - notify: + description: ID of a status report to link (optional). + openSlackChannel: type: - boolean - "null" - title: notify - description: Whether to notify subscribers about this maintenance (optional, defaults to false). - title: CreateMaintenanceRequest + title: open_slack_channel + description: Whether to open a Slack channel for the incident when Slack is connected (optional, defaults to false). + title: DeclareIncidentRequest additionalProperties: false - description: CreateMaintenanceRequest is the request to create a new maintenance window. - openstatus.maintenance.v1.CreateMaintenanceResponse: + description: DeclareIncidentRequest is the request to declare a new incident. + openstatus.incident.v1.DeclareIncidentResponse: type: object properties: - maintenance: - title: maintenance - description: The created maintenance. - $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' - title: CreateMaintenanceResponse + incident: + title: incident + description: The declared incident. + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: DeclareIncidentResponse additionalProperties: false - description: CreateMaintenanceResponse is the response after creating a maintenance window. - openstatus.maintenance.v1.DeleteMaintenanceRequest: + description: DeclareIncidentResponse is the response after declaring an incident. + openstatus.incident.v1.DeleteIncidentRequest: type: object properties: id: type: string title: id minLength: 1 - description: ID of the maintenance to delete (required). - title: DeleteMaintenanceRequest + description: ID of the incident (required). + title: DeleteIncidentRequest additionalProperties: false - description: DeleteMaintenanceRequest is the request to delete a maintenance window. - openstatus.maintenance.v1.DeleteMaintenanceResponse: + description: DeleteIncidentRequest is the request to delete an incident. + openstatus.incident.v1.DeleteIncidentResponse: type: object properties: success: type: boolean title: success description: Whether the deletion was successful. - title: DeleteMaintenanceResponse + title: DeleteIncidentResponse additionalProperties: false - description: DeleteMaintenanceResponse is the response after deleting a maintenance window. - openstatus.maintenance.v1.GetMaintenanceRequest: + description: DeleteIncidentResponse is the response after deleting an incident. + openstatus.incident.v1.GetIncidentRequest: type: object properties: id: type: string title: id minLength: 1 - description: ID of the maintenance to retrieve (required). - title: GetMaintenanceRequest + description: ID of the incident to retrieve (required). + title: GetIncidentRequest additionalProperties: false - description: GetMaintenanceRequest is the request to get a maintenance window by ID. - openstatus.maintenance.v1.GetMaintenanceResponse: + description: GetIncidentRequest is the request to get an incident by ID. + openstatus.incident.v1.GetIncidentResponse: type: object properties: - maintenance: - title: maintenance - description: The requested maintenance. - $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' - title: GetMaintenanceResponse + incident: + title: incident + description: The requested incident. + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: GetIncidentResponse additionalProperties: false - description: GetMaintenanceResponse is the response containing the maintenance window. - openstatus.maintenance.v1.ListMaintenancesRequest: + description: GetIncidentResponse is the response containing the incident and its timeline. + openstatus.incident.v1.GetPostmortemRequest: type: object properties: - limit: - type: - - integer - - "null" - title: limit - maximum: 100 - minimum: 1 - format: int32 - description: Maximum number of maintenances to return (1-100, defaults to 50). - offset: - type: - - integer - - "null" - title: offset - minimum: 0 - format: int32 - description: Number of maintenances to skip for pagination (defaults to 0). - pageId: - type: - - string - - "null" - title: page_id - description: Filter by page ID (optional). - title: ListMaintenancesRequest + incidentId: + type: string + title: incident_id + minLength: 1 + description: ID of the incident (required). + title: GetPostmortemRequest additionalProperties: false - description: ListMaintenancesRequest is the request to list maintenance windows. - openstatus.maintenance.v1.ListMaintenancesResponse: + description: GetPostmortemRequest is the request to get the postmortem of an incident. + openstatus.incident.v1.GetPostmortemResponse: type: object properties: - maintenances: - type: array - items: - $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary' - title: maintenances - description: List of maintenances. - totalSize: - type: integer - title: total_size - format: int32 - description: Total number of maintenances matching the filter. - title: ListMaintenancesResponse + postmortem: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.Postmortem' + - type: "null" + title: postmortem + description: The postmortem (unset when none has been written yet). + title: GetPostmortemResponse additionalProperties: false - description: ListMaintenancesResponse is the response containing maintenance window summaries. - openstatus.maintenance.v1.Maintenance: + description: GetPostmortemResponse is the response containing the postmortem, if any. + openstatus.incident.v1.Incident: type: object properties: id: type: string title: id - description: Unique identifier for the maintenance. + description: Unique identifier for the incident. title: type: string title: title - description: Title of the maintenance. - message: - type: string - title: message - description: Message describing the maintenance. - from: - type: string - title: from - description: Start time of the maintenance window (RFC 3339 format). - to: + description: Title of the incident. + severity: + title: severity + description: Severity of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + status: + title: status + description: Current status of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + summary: + type: + - string + - "null" + title: summary + description: Short human summary. + commander: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: commander + description: Member leading the response (unset when unassigned). + declaredBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: declared_by + description: Member who declared the incident (unset for API keys without a creator). + resolvedBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: resolved_by + description: Member who last resolved the incident. + declaredAt: type: string - title: to - description: End time of the maintenance window (RFC 3339 format). - pageId: + title: declared_at + description: Timestamp when the incident was declared (RFC 3339 format). + startedAt: type: string - title: page_id - description: ID of the page this maintenance is associated with. - pageComponentIds: + title: started_at + description: Timestamp when the impact began (RFC 3339 format). + mitigatedAt: + type: + - string + - "null" + title: mitigated_at + description: Timestamp when the incident was first mitigated (RFC 3339 format). + resolvedAt: + type: + - string + - "null" + title: resolved_at + description: Timestamp of the last resolution (RFC 3339 format). + closedAt: + type: + - string + - "null" + title: closed_at + description: |- + Timestamp when the incident was closed or canceled (RFC 3339 format). + A closed incident is read-only except for its postmortem. + statusReport: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatusReport' + - type: "null" + title: status_report + description: Linked public status report. + slackChannelUrl: + type: + - string + - "null" + title: slack_channel_url + description: Link to the bound Slack channel. + allowedTransitions: type: array items: - type: string - title: page_component_ids - description: IDs of affected page components. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + title: allowed_transitions + description: Statuses SetIncidentStatus accepts from the current state (empty once closed). + deletable: + type: boolean + title: deletable + description: 'Whether DeleteIncident is allowed: only while open and never mitigated, resolved or closed.' + events: + type: array + items: + $ref: '#/components/schemas/openstatus.incident.v1.IncidentEvent' + title: events + description: Timeline, newest first (only included in GetIncident). createdAt: type: string title: created_at - description: Timestamp when the maintenance was created (RFC 3339 format). + description: Timestamp when the incident was created (RFC 3339 format). updatedAt: type: string title: updated_at - description: Timestamp when the maintenance was last updated (RFC 3339 format). - title: Maintenance + description: Timestamp when the incident was last updated (RFC 3339 format). + title: Incident additionalProperties: false - description: Maintenance represents a maintenance window with full details. - openstatus.maintenance.v1.MaintenanceSummary: + description: Incident is a managed incident with full details. + openstatus.incident.v1.IncidentEvent: type: object properties: id: type: string title: id - description: Unique identifier for the maintenance. - title: - type: string - title: title - description: Title of the maintenance. + description: Unique identifier for the event. + type: + title: type + description: Kind of event. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentEventType' message: type: string title: message - description: Message describing the maintenance. - from: + description: 'Text of the event: the note for notes, a rendered summary otherwise.' + createdBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: created_by + description: Member who caused the event (unset for system events and API keys without a creator). + createdAt: type: string - title: from - description: Start time of the maintenance window (RFC 3339 format). - to: + title: created_at + description: Timestamp when the event was recorded (RFC 3339 format). + title: IncidentEvent + additionalProperties: false + description: IncidentEvent is one entry of an incident timeline. + openstatus.incident.v1.IncidentEventType: + type: string + title: IncidentEventType + enum: + - INCIDENT_EVENT_TYPE_UNSPECIFIED + - INCIDENT_EVENT_TYPE_DECLARED + - INCIDENT_EVENT_TYPE_SEVERITY_CHANGED + - INCIDENT_EVENT_TYPE_STATUS_CHANGED + - INCIDENT_EVENT_TYPE_COMMANDER_CHANGED + - INCIDENT_EVENT_TYPE_STARTED_AT_CHANGED + - INCIDENT_EVENT_TYPE_NOTE + - INCIDENT_EVENT_TYPE_STATUS_REPORT_LINKED + - INCIDENT_EVENT_TYPE_STATUS_REPORT_UNLINKED + - INCIDENT_EVENT_TYPE_SLACK_CHANNEL_BOUND + - INCIDENT_EVENT_TYPE_SLACK_CHANNEL_UNBOUND + - INCIDENT_EVENT_TYPE_RESOLVED + - INCIDENT_EVENT_TYPE_CANCELED + - INCIDENT_EVENT_TYPE_POSTMORTEM_DRAFTED + - INCIDENT_EVENT_TYPE_POSTMORTEM_UPDATED + - INCIDENT_EVENT_TYPE_POSTMORTEM_APPROVED + - INCIDENT_EVENT_TYPE_CLOSED + description: IncidentEventType is the kind of entry in an incident timeline. + openstatus.incident.v1.IncidentSeverity: + type: string + title: IncidentSeverity + enum: + - INCIDENT_SEVERITY_UNSPECIFIED + - INCIDENT_SEVERITY_CRITICAL + - INCIDENT_SEVERITY_MAJOR + - INCIDENT_SEVERITY_MINOR + description: IncidentSeverity is how bad an incident is. + openstatus.incident.v1.IncidentStatus: + type: string + title: IncidentStatus + enum: + - INCIDENT_STATUS_UNSPECIFIED + - INCIDENT_STATUS_OPEN + - INCIDENT_STATUS_MITIGATED + - INCIDENT_STATUS_RESOLVED + - INCIDENT_STATUS_CANCELED + description: |- + IncidentStatus is the lifecycle state of an incident. + Closed is not a status: a closed incident has closed_at set and keeps its last status. + openstatus.incident.v1.IncidentStatusReport: + type: object + properties: + id: type: string - title: to - description: End time of the maintenance window (RFC 3339 format). + title: id + description: ID of the status report. + title: + type: string + title: title + description: Title of the status report. + status: + title: status + description: Current status of the status report. + $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus' pageId: type: string title: page_id - description: ID of the page this maintenance is associated with. - pageComponentIds: + description: ID of the status page the report belongs to. + title: IncidentStatusReport + additionalProperties: false + description: IncidentStatusReport is the public status report linked to an incident. + openstatus.incident.v1.IncidentSummary: + type: object + properties: + id: + type: string + title: id + description: Unique identifier for the incident. + title: + type: string + title: title + description: Title of the incident. + severity: + title: severity + description: Severity of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + status: + title: status + description: Current status of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + commander: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: commander + description: Member leading the response (unset when unassigned). + declaredAt: + type: string + title: declared_at + description: Timestamp when the incident was declared (RFC 3339 format). + startedAt: + type: string + title: started_at + description: Timestamp when the impact began (RFC 3339 format). + resolvedAt: + type: + - string + - "null" + title: resolved_at + description: Timestamp of the last resolution (RFC 3339 format). + closedAt: + type: + - string + - "null" + title: closed_at + description: Timestamp when the incident was closed or canceled (RFC 3339 format). + statusReport: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatusReport' + - type: "null" + title: status_report + description: Linked public status report. + createdAt: + type: string + title: created_at + description: Timestamp when the incident was created (RFC 3339 format). + updatedAt: + type: string + title: updated_at + description: Timestamp when the incident was last updated (RFC 3339 format). + title: IncidentSummary + additionalProperties: false + description: IncidentSummary is the metadata of an incident (used in list responses). + openstatus.incident.v1.IncidentUser: + type: object + properties: + email: + type: string + title: email + description: Email address of the member (empty for a deleted account). + name: + type: string + title: name + description: Display name of the member ("Deleted user" for a deleted account). + title: IncidentUser + additionalProperties: false + description: IncidentUser is a workspace member referenced by an incident. + openstatus.incident.v1.LinkStatusReportRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident (required). + statusReportId: + type: string + title: status_report_id + minLength: 1 + description: ID of the status report to link (required). + title: LinkStatusReportRequest + additionalProperties: false + description: LinkStatusReportRequest is the request to link a status report to an incident. + openstatus.incident.v1.LinkStatusReportResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: LinkStatusReportResponse + additionalProperties: false + description: LinkStatusReportResponse is the response after linking a status report. + openstatus.incident.v1.ListIncidentsRequest: + type: object + properties: + limit: + type: + - integer + - "null" + title: limit + maximum: 100 + minimum: 1 + format: int32 + description: Maximum number of incidents to return (1-100, defaults to 50). + offset: + type: + - integer + - "null" + title: offset + minimum: 0 + format: int32 + description: Number of incidents to skip for pagination (defaults to 0). + statuses: type: array items: - type: string - title: page_component_ids - description: IDs of affected page components. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + title: statuses + description: Filter by status (optional). If empty, returns all statuses. + closed: + type: + - boolean + - "null" + title: closed + description: Filter by closed state (optional). If unset, returns both. + title: ListIncidentsRequest + additionalProperties: false + description: ListIncidentsRequest is the request to list incidents. + openstatus.incident.v1.ListIncidentsResponse: + type: object + properties: + incidents: + type: array + items: + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSummary' + title: incidents + description: List of incidents (metadata only, use GetIncident for full details). + totalSize: + type: integer + title: total_size + format: int32 + description: Total number of incidents matching the filter. + title: ListIncidentsResponse + additionalProperties: false + description: ListIncidentsResponse is the response containing incident summaries. + openstatus.incident.v1.Postmortem: + type: object + properties: + incidentId: + type: string + title: incident_id + description: ID of the incident the postmortem belongs to. + status: + title: status + description: Review state of the postmortem. + $ref: '#/components/schemas/openstatus.incident.v1.PostmortemStatus' + content: + type: string + title: content + description: Markdown body of the postmortem. + draftedBy: + title: drafted_by + description: Who wrote the current body. + $ref: '#/components/schemas/openstatus.incident.v1.PostmortemAuthor' + approvedBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: approved_by + description: Member who approved the postmortem. + approvedAt: + type: + - string + - "null" + title: approved_at + description: Timestamp when the postmortem was approved (RFC 3339 format). createdAt: type: string title: created_at - description: Timestamp when the maintenance was created (RFC 3339 format). + description: Timestamp when the postmortem was created (RFC 3339 format). updatedAt: type: string title: updated_at - description: Timestamp when the maintenance was last updated (RFC 3339 format). - title: MaintenanceSummary + description: Timestamp when the postmortem was last updated (RFC 3339 format). + title: Postmortem additionalProperties: false - description: MaintenanceSummary represents metadata for a maintenance window (used in list responses). - openstatus.maintenance.v1.UpdateMaintenanceRequest: + description: Postmortem is the review written after an incident is resolved. + openstatus.incident.v1.PostmortemAuthor: + type: string + title: PostmortemAuthor + enum: + - POSTMORTEM_AUTHOR_UNSPECIFIED + - POSTMORTEM_AUTHOR_AGENT + - POSTMORTEM_AUTHOR_USER + description: PostmortemAuthor is who wrote the current postmortem body. + openstatus.incident.v1.PostmortemStatus: + type: string + title: PostmortemStatus + enum: + - POSTMORTEM_STATUS_UNSPECIFIED + - POSTMORTEM_STATUS_DRAFT + - POSTMORTEM_STATUS_APPROVED + description: PostmortemStatus is the review state of a postmortem. + openstatus.incident.v1.SetIncidentStatusRequest: type: object properties: id: type: string title: id minLength: 1 - description: ID of the maintenance to update (required). + description: ID of the incident (required). + status: + not: + enum: + - INCIDENT_STATUS_UNSPECIFIED + title: status + description: Target status (required). + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + note: + type: + - string + - "null" + title: note + maxLength: 10000 + description: Note recorded with the change (optional, up to 10000 characters). + title: SetIncidentStatusRequest + additionalProperties: false + description: SetIncidentStatusRequest is the request to change the status of an incident. + openstatus.incident.v1.SetIncidentStatusResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: SetIncidentStatusResponse + additionalProperties: false + description: SetIncidentStatusResponse is the response after changing the status of an incident. + openstatus.incident.v1.UnlinkStatusReportRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident (required). + title: UnlinkStatusReportRequest + additionalProperties: false + description: UnlinkStatusReportRequest is the request to unlink the status report of an incident. + openstatus.incident.v1.UnlinkStatusReportResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: UnlinkStatusReportResponse + additionalProperties: false + description: UnlinkStatusReportResponse is the response after unlinking the status report. + openstatus.incident.v1.UpdateIncidentRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident to update (required). title: type: - string @@ -369,79 +823,414 @@ components: title: title maxLength: 256 minLength: 1 - description: New title for the maintenance (optional). - message: + description: New title (optional, 1-256 characters). + severity: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + - type: "null" + not: + enum: + - INCIDENT_SEVERITY_UNSPECIFIED + title: severity + description: New severity (optional). + summary: type: - string - "null" - title: message - description: New message for the maintenance (optional). - from: + title: summary + maxLength: 4000 + minLength: 1 + description: New summary (optional, 1-4000 characters). + clearSummary: + type: + - boolean + - "null" + title: clear_summary + description: Set to true to remove the summary. Cannot be combined with summary. + commanderEmail: type: - string - "null" - title: from - pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: New start time (RFC 3339 format, optional). - to: + title: commander_email + format: email + description: Email of the new commander (optional). + clearCommander: + type: + - boolean + - "null" + title: clear_commander + description: Set to true to unassign the commander. Cannot be combined with commander_email. + startedAt: type: - string - "null" + title: started_at + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: New start of impact (RFC 3339 format, optional). + title: UpdateIncidentRequest + additionalProperties: false + description: UpdateIncidentRequest is the request to edit an incident. + openstatus.incident.v1.UpdateIncidentResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: UpdateIncidentResponse + additionalProperties: false + description: UpdateIncidentResponse is the response after updating an incident. + openstatus.incident.v1.UpdatePostmortemRequest: + type: object + properties: + incidentId: + type: string + title: incident_id + minLength: 1 + description: ID of the incident (required). + content: + type: string + title: content + maxLength: 100000 + minLength: 1 + description: Markdown body (required, 1-100000 characters). Replaces the current body; an approved postmortem stays approved. + title: UpdatePostmortemRequest + additionalProperties: false + description: UpdatePostmortemRequest is the request to write the postmortem of a resolved incident. + openstatus.incident.v1.UpdatePostmortemResponse: + type: object + properties: + postmortem: + title: postmortem + description: The saved postmortem. + $ref: '#/components/schemas/openstatus.incident.v1.Postmortem' + title: UpdatePostmortemResponse + additionalProperties: false + description: UpdatePostmortemResponse is the response after writing the postmortem. + openstatus.maintenance.v1.CreateMaintenanceRequest: + type: object + properties: + title: + type: string + examples: + - Database Migration + title: title + maxLength: 256 + minLength: 1 + description: Title of the maintenance (required, 1-256 characters). + message: + type: string + title: message + minLength: 1 + description: Message describing the maintenance (required). + from: + type: string + examples: + - "2024-03-01T02:00:00Z" + title: from + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: Start time of the maintenance window (RFC 3339 format, required). + to: + type: string + examples: + - "2024-03-01T06:00:00Z" title: to pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: New end time (RFC 3339 format, optional). + description: End time of the maintenance window (RFC 3339 format, required). pageId: - type: - - string - - "null" + type: string title: page_id - description: 'Deprecated: page_id is now derived from page_component_ids.' - deprecated: true + minLength: 1 + description: Page ID to associate with this maintenance (required). pageComponentIds: type: array items: type: string title: page_component_ids - description: New list of page component IDs (optional, replaces existing list). - updatePageComponentIds: + description: Page component IDs to associate with this maintenance (optional). + notify: type: - boolean - "null" - title: update_page_component_ids - description: |- - Set to true to update page component associations. - When true, page_component_ids replaces the existing list (empty clears all). - When false or unset, page_component_ids is ignored and existing associations are preserved. - title: UpdateMaintenanceRequest + title: notify + description: Whether to notify subscribers about this maintenance (optional, defaults to false). + title: CreateMaintenanceRequest additionalProperties: false - description: UpdateMaintenanceRequest is the request to update a maintenance window. - openstatus.maintenance.v1.UpdateMaintenanceResponse: + description: CreateMaintenanceRequest is the request to create a new maintenance window. + openstatus.maintenance.v1.CreateMaintenanceResponse: type: object properties: maintenance: title: maintenance - description: The updated maintenance. + description: The created maintenance. $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' - title: UpdateMaintenanceResponse + title: CreateMaintenanceResponse additionalProperties: false - description: UpdateMaintenanceResponse is the response after updating a maintenance window. - openstatus.monitor.v1.BodyAssertion: + description: CreateMaintenanceResponse is the response after creating a maintenance window. + openstatus.maintenance.v1.DeleteMaintenanceRequest: type: object properties: - target: + id: type: string - title: target - description: Target value to compare against. - comparator: - not: - enum: - - STRING_COMPARATOR_UNSPECIFIED - title: comparator - description: Comparison operation (required, must not be UNSPECIFIED). - $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator' - title: BodyAssertion + title: id + minLength: 1 + description: ID of the maintenance to delete (required). + title: DeleteMaintenanceRequest additionalProperties: false - description: BodyAssertion defines an assertion for response body content. + description: DeleteMaintenanceRequest is the request to delete a maintenance window. + openstatus.maintenance.v1.DeleteMaintenanceResponse: + type: object + properties: + success: + type: boolean + title: success + description: Whether the deletion was successful. + title: DeleteMaintenanceResponse + additionalProperties: false + description: DeleteMaintenanceResponse is the response after deleting a maintenance window. + openstatus.maintenance.v1.GetMaintenanceRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the maintenance to retrieve (required). + title: GetMaintenanceRequest + additionalProperties: false + description: GetMaintenanceRequest is the request to get a maintenance window by ID. + openstatus.maintenance.v1.GetMaintenanceResponse: + type: object + properties: + maintenance: + title: maintenance + description: The requested maintenance. + $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' + title: GetMaintenanceResponse + additionalProperties: false + description: GetMaintenanceResponse is the response containing the maintenance window. + openstatus.maintenance.v1.ListMaintenancesRequest: + type: object + properties: + limit: + type: + - integer + - "null" + title: limit + maximum: 100 + minimum: 1 + format: int32 + description: Maximum number of maintenances to return (1-100, defaults to 50). + offset: + type: + - integer + - "null" + title: offset + minimum: 0 + format: int32 + description: Number of maintenances to skip for pagination (defaults to 0). + pageId: + type: + - string + - "null" + title: page_id + description: Filter by page ID (optional). + title: ListMaintenancesRequest + additionalProperties: false + description: ListMaintenancesRequest is the request to list maintenance windows. + openstatus.maintenance.v1.ListMaintenancesResponse: + type: object + properties: + maintenances: + type: array + items: + $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary' + title: maintenances + description: List of maintenances. + totalSize: + type: integer + title: total_size + format: int32 + description: Total number of maintenances matching the filter. + title: ListMaintenancesResponse + additionalProperties: false + description: ListMaintenancesResponse is the response containing maintenance window summaries. + openstatus.maintenance.v1.Maintenance: + type: object + properties: + id: + type: string + title: id + description: Unique identifier for the maintenance. + title: + type: string + title: title + description: Title of the maintenance. + message: + type: string + title: message + description: Message describing the maintenance. + from: + type: string + title: from + description: Start time of the maintenance window (RFC 3339 format). + to: + type: string + title: to + description: End time of the maintenance window (RFC 3339 format). + pageId: + type: string + title: page_id + description: ID of the page this maintenance is associated with. + pageComponentIds: + type: array + items: + type: string + title: page_component_ids + description: IDs of affected page components. + createdAt: + type: string + title: created_at + description: Timestamp when the maintenance was created (RFC 3339 format). + updatedAt: + type: string + title: updated_at + description: Timestamp when the maintenance was last updated (RFC 3339 format). + title: Maintenance + additionalProperties: false + description: Maintenance represents a maintenance window with full details. + openstatus.maintenance.v1.MaintenanceSummary: + type: object + properties: + id: + type: string + title: id + description: Unique identifier for the maintenance. + title: + type: string + title: title + description: Title of the maintenance. + message: + type: string + title: message + description: Message describing the maintenance. + from: + type: string + title: from + description: Start time of the maintenance window (RFC 3339 format). + to: + type: string + title: to + description: End time of the maintenance window (RFC 3339 format). + pageId: + type: string + title: page_id + description: ID of the page this maintenance is associated with. + pageComponentIds: + type: array + items: + type: string + title: page_component_ids + description: IDs of affected page components. + createdAt: + type: string + title: created_at + description: Timestamp when the maintenance was created (RFC 3339 format). + updatedAt: + type: string + title: updated_at + description: Timestamp when the maintenance was last updated (RFC 3339 format). + title: MaintenanceSummary + additionalProperties: false + description: MaintenanceSummary represents metadata for a maintenance window (used in list responses). + openstatus.maintenance.v1.UpdateMaintenanceRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the maintenance to update (required). + title: + type: + - string + - "null" + title: title + maxLength: 256 + minLength: 1 + description: New title for the maintenance (optional). + message: + type: + - string + - "null" + title: message + description: New message for the maintenance (optional). + from: + type: + - string + - "null" + title: from + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: New start time (RFC 3339 format, optional). + to: + type: + - string + - "null" + title: to + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: New end time (RFC 3339 format, optional). + pageId: + type: + - string + - "null" + title: page_id + description: 'Deprecated: page_id is now derived from page_component_ids.' + deprecated: true + pageComponentIds: + type: array + items: + type: string + title: page_component_ids + description: New list of page component IDs (optional, replaces existing list). + updatePageComponentIds: + type: + - boolean + - "null" + title: update_page_component_ids + description: |- + Set to true to update page component associations. + When true, page_component_ids replaces the existing list (empty clears all). + When false or unset, page_component_ids is ignored and existing associations are preserved. + title: UpdateMaintenanceRequest + additionalProperties: false + description: UpdateMaintenanceRequest is the request to update a maintenance window. + openstatus.maintenance.v1.UpdateMaintenanceResponse: + type: object + properties: + maintenance: + title: maintenance + description: The updated maintenance. + $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' + title: UpdateMaintenanceResponse + additionalProperties: false + description: UpdateMaintenanceResponse is the response after updating a maintenance window. + openstatus.monitor.v1.BodyAssertion: + type: object + properties: + target: + type: string + title: target + description: Target value to compare against. + comparator: + not: + enum: + - STRING_COMPARATOR_UNSPECIFIED + title: comparator + description: Comparison operation (required, must not be UNSPECIFIED). + $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator' + title: BodyAssertion + additionalProperties: false + description: BodyAssertion defines an assertion for response body content. openstatus.monitor.v1.CreateDNSMonitorRequest: type: object properties: @@ -4852,8 +5641,17 @@ components: Per-component impacts set by the initial update (optional). When provided, the named components are added to the report's affected set. Omitting this field creates a legacy report without impact tracking. - title: CreateStatusReportRequest - additionalProperties: false + incidentId: + type: + - string + - "null" + title: incident_id + minLength: 1 + description: |- + ID of an incident to link the report to (optional). Linked in the same + step: a closed incident, or one already linked to a report, fails the create. + title: CreateStatusReportRequest + additionalProperties: false description: CreateStatusReportRequest is the request to create a new status report. openstatus.status_report.v1.CreateStatusReportResponse: type: object @@ -5000,6 +5798,12 @@ components: type: string title: updated_at description: Timestamp when the report was last updated (RFC 3339 format). + incidentId: + type: + - string + - "null" + title: incident_id + description: ID of the incident this report communicates (unset when not linked). title: StatusReport additionalProperties: false description: StatusReport represents an incident or maintenance report with full details. @@ -5042,6 +5846,12 @@ components: type: string title: updated_at description: Timestamp when the report was last updated (RFC 3339 format). + incidentId: + type: + - string + - "null" + title: incident_id + description: ID of the incident this report communicates (unset when not linked). title: StatusReportSummary additionalProperties: false description: StatusReportSummary represents metadata for a status report (used in list responses). @@ -5136,8 +5946,12 @@ tags: and associate them with monitors. Supports 12 notification providers. - name: StatusReportService description: | - Create and manage incident reports with status updates. Reports follow a lifecycle: + Create and manage public status reports with status updates. Reports follow a lifecycle: investigating -> identified -> monitoring -> resolved. + - name: IncidentService + description: | + Declare and run incidents: lifecycle (open -> mitigated -> resolved, or canceled), + timeline notes, a linked status report, and the postmortem. - name: MaintenanceService description: | Schedule maintenance windows for status page components. Subscribers can be @@ -5205,6 +6019,454 @@ paths: application/json: schema: $ref: '#/components/schemas/openstatus.health.v1.CheckResponse' + /rpc/openstatus.incident.v1.IncidentService/AddIncidentNote: + post: + tags: + - IncidentService + summary: AddIncidentNote + description: AddIncidentNote appends a note to the incident timeline. + operationId: IncidentService_AddIncidentNote + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.AddIncidentNoteRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.AddIncidentNoteResponse' + /rpc/openstatus.incident.v1.IncidentService/ApprovePostmortem: + post: + tags: + - IncidentService + summary: ApprovePostmortem + description: Approves the draft postmortem of a resolved incident. With close set to true the incident is closed in the same step. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this. + operationId: IncidentService_ApprovePostmortem + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ApprovePostmortemRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ApprovePostmortemResponse' + /rpc/openstatus.incident.v1.IncidentService/CloseIncident: + post: + tags: + - IncidentService + summary: CloseIncident + description: Closes a resolved incident, after which only its postmortem can change. Requires an approved postmortem, or skip_postmortem set to true. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this. + operationId: IncidentService_CloseIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.CloseIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.CloseIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/DeclareIncident: + post: + tags: + - IncidentService + summary: DeclareIncident + description: Declares a new incident in the open status. The commander is set by email and must be a member of the workspace; without one the incident is unassigned. A newly assigned commander is emailed unless they own the API key. When open_slack_channel is true and Slack is connected, a dedicated channel is created and the team invited. started_at defaults to now and can be set in the past for a retroactive declare. + operationId: IncidentService_DeclareIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeclareIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeclareIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/DeleteIncident: + post: + tags: + - IncidentService + summary: DeleteIncident + description: Deletes an incident declared by mistake. Only allowed while the incident is open and was never mitigated, resolved or closed (Incident.deletable); cancel it otherwise. Allowed for workspace owners and admins only. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this. + operationId: IncidentService_DeleteIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeleteIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeleteIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/GetIncident: + get: + tags: + - IncidentService + summary: GetIncident + description: GetIncident retrieves an incident by ID, including its timeline. + operationId: IncidentService_GetIncident.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentRequest' + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentResponse' + post: + tags: + - IncidentService + summary: GetIncident + description: GetIncident retrieves an incident by ID, including its timeline. + operationId: IncidentService_GetIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/GetPostmortem: + get: + tags: + - IncidentService + summary: GetPostmortem + description: GetPostmortem retrieves the postmortem of an incident, if any. + operationId: IncidentService_GetPostmortem.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemRequest' + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemResponse' + post: + tags: + - IncidentService + summary: GetPostmortem + description: GetPostmortem retrieves the postmortem of an incident, if any. + operationId: IncidentService_GetPostmortem + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemResponse' + /rpc/openstatus.incident.v1.IncidentService/LinkStatusReport: + post: + tags: + - IncidentService + summary: LinkStatusReport + description: LinkStatusReport links a public status report to the incident. + operationId: IncidentService_LinkStatusReport + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.LinkStatusReportRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.LinkStatusReportResponse' + /rpc/openstatus.incident.v1.IncidentService/ListIncidents: + get: + tags: + - IncidentService + summary: ListIncidents + description: ListIncidents returns the incidents of the workspace (metadata only), open first. + operationId: IncidentService_ListIncidents.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsRequest' + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsResponse' + post: + tags: + - IncidentService + summary: ListIncidents + description: ListIncidents returns the incidents of the workspace (metadata only), open first. + operationId: IncidentService_ListIncidents + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsResponse' + /rpc/openstatus.incident.v1.IncidentService/SetIncidentStatus: + post: + tags: + - IncidentService + summary: SetIncidentStatus + description: 'Moves an incident to a new status. Allowed transitions: open -> mitigated, resolved or canceled; mitigated -> resolved, open or canceled; resolved -> open. Canceled is terminal and closes the incident. The same status, a forbidden transition or a closed incident fail with failed_precondition; Incident.allowed_transitions lists what is valid. A linked status report is never updated: post the public update with StatusReportService.AddStatusReportUpdate.' + operationId: IncidentService_SetIncidentStatus + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.SetIncidentStatusRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.SetIncidentStatusResponse' + /rpc/openstatus.incident.v1.IncidentService/UnlinkStatusReport: + post: + tags: + - IncidentService + summary: UnlinkStatusReport + description: UnlinkStatusReport removes the link to the incident's status report. + operationId: IncidentService_UnlinkStatusReport + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UnlinkStatusReportRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UnlinkStatusReportResponse' + /rpc/openstatus.incident.v1.IncidentService/UpdateIncident: + post: + tags: + - IncidentService + summary: UpdateIncident + description: UpdateIncident edits the title, severity, summary, commander or start time of an incident. + operationId: IncidentService_UpdateIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdateIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdateIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/UpdatePostmortem: + post: + tags: + - IncidentService + summary: UpdatePostmortem + description: UpdatePostmortem creates or replaces the postmortem body of a resolved incident. + operationId: IncidentService_UpdatePostmortem + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdatePostmortemRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdatePostmortemResponse' /rpc/openstatus.maintenance.v1.MaintenanceService/CreateMaintenance: post: tags: diff --git a/apps/server/test.importmap.json b/apps/server/test.importmap.json index e2cdceab..fb4eacfb 100644 --- a/apps/server/test.importmap.json +++ b/apps/server/test.importmap.json @@ -8,6 +8,8 @@ "@openstatus/subscriptions-real": "../../packages/subscriptions/src/index.ts", "@openstatus/analytics": "./src/libs/test/doubles/analytics.mock.ts", "@openstatus/analytics-real": "../../packages/analytics/src/index.ts", + "@openstatus/emails": "./src/libs/test/doubles/emails.mock.ts", + "@openstatus/emails-real": "../../packages/emails/src/index.ts", "./src/routes/slack/page-urls": "./src/libs/test/doubles/page-urls.mock.ts", "./src/routes/slack/workspace-resolver": "./src/libs/test/doubles/workspace-resolver.mock.ts", "./src/routes/slack/agent": "./src/libs/test/doubles/slack-agent.mock.ts", diff --git a/apps/web/src/content/docs.config.ts b/apps/web/src/content/docs.config.ts index efd216b7..3f76d6da 100644 --- a/apps/web/src/content/docs.config.ts +++ b/apps/web/src/content/docs.config.ts @@ -215,6 +215,10 @@ export const docsNav: DocsNavSection[] = [ slug: "sdk/nodejs/maintenance-service", label: "Maintenance Service", }, + { + slug: "sdk/nodejs/incident-service", + label: "Incident Service", + }, { slug: "sdk/nodejs/notification-service", label: "Notification Service", diff --git a/apps/web/src/content/pages/changelog/incident-api.mdx b/apps/web/src/content/pages/changelog/incident-api.mdx new file mode 100644 index 00000000..88d6c409 --- /dev/null +++ b/apps/web/src/content/pages/changelog/incident-api.mdx @@ -0,0 +1,24 @@ +--- +title: "Incident API" +description: "Declare and run incidents from the API: lifecycle, timeline notes, status report link and postmortem." +publishedAt: "2026-10-02" +category: "incidents" +author: "openstatus" +--- + +Incidents are now available on the API, through the new `IncidentService`. + +Everything you do from the dashboard works from your scripts and integrations too: + +- Declare an incident with a severity, a commander (by email) and an optional Slack channel. +- Move it through `open`, `mitigated`, `resolved` or `canceled`, with a note on each change. +- Add timeline notes, link the public status report, and write and approve the postmortem. +- Close the incident, or delete one declared by mistake. + +Changes made through the API send the same commander emails and Slack channel messages as the dashboard. `createStatusReport` now takes an `incidentId`, and status reports return the incident they are linked to. + +Read the [Incident Service guide](/docs/sdk/nodejs/incident-service) to get started. + +## Error code change + +Requests that conflict with the current state of a resource now fail with `failed_precondition` instead of `invalid_argument`, across every service. This covers cases such as a status change the lifecycle does not allow or a report that is already linked. If your integration checks for `invalid_argument` on those errors, update it. diff --git a/apps/web/src/content/pages/docs/sdk/nodejs/incident-service.mdx b/apps/web/src/content/pages/docs/sdk/nodejs/incident-service.mdx new file mode 100644 index 00000000..fc133984 --- /dev/null +++ b/apps/web/src/content/pages/docs/sdk/nodejs/incident-service.mdx @@ -0,0 +1,181 @@ +--- +category: SDK +title: Incident Service +description: "Declare and run incidents with the openstatus Node.js SDK: lifecycle, timeline, status report link and postmortem" +--- + +Declare and run incidents: move them through their lifecycle, keep a timeline, link the public status report and write the postmortem. The Incident Service provides 13 RPC methods. + +The `IncidentService` is live on the API (`/rpc/openstatus.incident.v1.IncidentService/*`, see the [API reference](https://api.openstatus.dev/openapi)). The examples below use the SDK client shape it will ship with in an upcoming release. + +An incident is your team's internal record of an outage. It is not public: to tell your users, link a [status report](/docs/sdk/nodejs/status-report-service). It is also not the same as monitor downtime, which openstatus detects on its own. + +## Declare an Incident + +```typescript +import { IncidentSeverity } from "@openstatus/sdk-node"; + +const { incident } = await client.incident.v1.IncidentService.declareIncident({ + title: "Checkout API returns 502", + severity: IncidentSeverity.MAJOR, + commanderEmail: "jane@example.com", + startedAt: "2024-03-15T10:30:00Z", + openSlackChannel: true, +}); + +console.log(`Incident #${incident?.id} declared`); +``` + +- `commanderEmail` must belong to a member of your workspace. Without it the incident has no commander. A newly assigned commander gets an email, unless they created the API key. +- `startedAt` defaults to now. Set it in the past to declare an outage after the fact. +- `openSlackChannel` creates a dedicated Slack channel when the Slack integration is connected. It defaults to `false`. +- `statusReportId` links an existing status report right away. + +## Lifecycle + +``` +open ──▶ mitigated ──▶ resolved ──▶ closed + │ ▲ + └──── (skip mitigated) ─┘ +``` + +| From | Allowed next statuses | +| --- | --- | +| `OPEN` | `MITIGATED`, `RESOLVED`, `CANCELED` | +| `MITIGATED` | `RESOLVED`, `OPEN`, `CANCELED` | +| `RESOLVED` | `OPEN` (reopen), or close with `closeIncident` | +| `CANCELED` | none: a false alarm, closed for good | + +Every incident carries `allowedTransitions`, so you don't have to hard-code this table. Closed is not a status: a closed incident has `closedAt` set and keeps `RESOLVED` (or `CANCELED`). After close, only the postmortem can change. + +### Change the Status + +```typescript +import { IncidentStatus } from "@openstatus/sdk-node"; + +await client.incident.v1.IncidentService.setIncidentStatus({ + id: "42", + status: IncidentStatus.MITIGATED, + note: "Rolled back the deploy, error rate is dropping.", +}); +``` + +The note becomes part of the timeline entry. Setting the status the incident already has, or a transition the table forbids, fails with `failed_precondition`. + +A linked status report is never updated for you. To tell your users the incident is over, post a resolved update with [`addStatusReportUpdate`](/docs/sdk/nodejs/status-report-service). + +### Add a Note + +```typescript +const { event } = await client.incident.v1.IncidentService.addIncidentNote({ + id: "42", + message: "Root cause looks like the connection pool limit.", +}); +``` + +## Get and List Incidents + +```typescript +const { incident } = await client.incident.v1.IncidentService.getIncident({ + id: "42", +}); + +for (const event of incident?.events ?? []) { + console.log(event.createdAt, event.type, event.message); +} +``` + +`getIncident` includes the full timeline, newest first. Other responses leave `events` empty. + +```typescript +const { incidents, totalSize } = await client.incident.v1.IncidentService + .listIncidents({ + statuses: [IncidentStatus.RESOLVED], + closed: false, + limit: 20, + }); +``` + +Incidents are listed open first, then by most recently declared. Combine `statuses: [RESOLVED]` with `closed: false` to find the incidents that still need a postmortem. + +How long an incident lasted is `startedAt` to `resolvedAt` (or `closedAt` when canceled), or to now while it is ongoing. + +## Update an Incident + +```typescript +await client.incident.v1.IncidentService.updateIncident({ + id: "42", + severity: IncidentSeverity.CRITICAL, + commanderEmail: "sam@example.com", +}); + +await client.incident.v1.IncidentService.updateIncident({ + id: "42", + clearCommander: true, + clearSummary: true, +}); +``` + +Fields you leave out are kept. Use `clearCommander` and `clearSummary` to remove a value. Sending a value together with its clear flag is rejected. + +## Link a Status Report + +```typescript +await client.incident.v1.IncidentService.linkStatusReport({ + id: "42", + statusReportId: "17", +}); + +await client.incident.v1.IncidentService.unlinkStatusReport({ id: "42" }); +``` + +An incident links to at most one status report, and a status report to at most one incident. You can also link while creating the report, by passing `incidentId` to `createStatusReport`. Status reports return their linked `incidentId`. + +## Postmortem + +Once an incident is resolved, write its postmortem as markdown: + +```typescript +await client.incident.v1.IncidentService.updatePostmortem({ + incidentId: "42", + content: "## Summary\n\nCheckout failed for 23 minutes...", +}); + +const { postmortem, incident } = await client.incident.v1.IncidentService + .approvePostmortem({ incidentId: "42", close: true }); +``` + +`updatePostmortem` replaces the body. Editing an approved postmortem keeps it approved. `approvePostmortem` with `close: true` approves the postmortem and closes the incident in one step. + +## Close and Delete + +```typescript +await client.incident.v1.IncidentService.closeIncident({ + id: "42", + skipPostmortem: true, +}); +``` + +Closing needs a resolved incident and an approved postmortem, or `skipPostmortem: true`. + +```typescript +await client.incident.v1.IncidentService.deleteIncident({ id: "42" }); +``` + +Delete is for incidents declared by mistake: it only works while the incident is open and was never mitigated, resolved or closed (`deletable` is true). Cancel the incident otherwise; that keeps the record. + +## Permissions + +All write methods need an API key with `write` scope. The key acts as the member who created it, with that member's current role: + +| Method | Who can call it | +| --- | --- | +| `closeIncident`, `approvePostmortem` | Workspace owners and admins, and the incident commander | +| `deleteIncident` | Workspace owners and admins | +| Everything else | Any member | + +Keys that were created before keys had an owner cannot close, delete or approve; they get `permission_denied`. Create a new key from the dashboard to get one tied to your account. + +## Slack + +When the [Slack agent](/docs/guides/how-to-setup-slack-agent) is connected, changes made through the API are announced in the incident's channel, and closing, canceling or deleting the incident archives it, just like changes made in the dashboard. Incidents return `slackChannelUrl` when a channel is bound. diff --git a/apps/web/src/content/pages/docs/sdk/nodejs/overview.mdx b/apps/web/src/content/pages/docs/sdk/nodejs/overview.mdx index 2d1527fc..c539c111 100644 --- a/apps/web/src/content/pages/docs/sdk/nodejs/overview.mdx +++ b/apps/web/src/content/pages/docs/sdk/nodejs/overview.mdx @@ -5,7 +5,7 @@ description: "Interact with openstatus programmatically from your JavaScript and --- -The openstatus Node.js SDK lets you manage monitors, status pages, status reports, maintenance windows, and notifications from JavaScript and TypeScript. Request and response messages are fully typed, generated from the openstatus protobuf schemas. +The openstatus Node.js SDK lets you manage monitors, status pages, status reports, maintenance windows, incidents, and notifications from JavaScript and TypeScript. Request and response messages are fully typed, generated from the openstatus protobuf schemas. The SDK is open source and developed in its own repository, [openstatusHQ/sdk-node](https://github.com/openstatusHQ/sdk-node) (separate from the main monorepo). @@ -13,7 +13,8 @@ The SDK is open source and developed in its own repository, [openstatusHQ/sdk-no - **HTTP, TCP, and DNS monitoring** — monitor websites, APIs, database connections, and DNS records from 28 locations worldwide. (ICMP and gRPC monitors exist in the API but are not yet exposed by this SDK.) - **Status pages** — create and manage public status pages with monitor-based or static components, grouping, and subscribers. -- **Status reports and maintenance** — manage incident reports with update timelines and schedule planned maintenance windows. +- **Status reports and maintenance** — manage public status reports with update timelines and schedule planned maintenance windows. +- **Incidents** — declare and run incidents with a timeline, a linked status report and a postmortem. (Available on the API; coming to this SDK in an upcoming release.) - **Notifications** — configure all 12 channels: Slack, Discord, Microsoft Teams, Email, WhatsApp, Telegram, PagerDuty, Opsgenie, Google Chat, Grafana OnCall, ntfy, and generic webhooks. - **Type-safe** — typed request/response messages generated from the openstatus protobuf schemas. - **Cross-runtime** — works on Node.js, Deno, and Bun, in both ESM and CJS. @@ -37,6 +38,7 @@ The client exposes one service per domain. Each has its own reference page: | Status Report | [Status Report Service](/docs/sdk/nodejs/status-report-service) | Manage incident reports and their update timelines. | | Status Page | [Status Page Service](/docs/sdk/nodejs/status-page-service) | Manage status pages, components, component groups, and subscribers. | | Maintenance | [Maintenance Service](/docs/sdk/nodejs/maintenance-service) | Schedule and manage planned maintenance windows. | +| Incident | [Incident Service](/docs/sdk/nodejs/incident-service) | Declare and run incidents: lifecycle, timeline, status report link and postmortem. | | Notification | [Notification Service](/docs/sdk/nodejs/notification-service) | Configure notification channels and check usage limits. | | Private Location | [Private Location Service](/docs/sdk/nodejs/private-location-service) | Manage self-hosted probes and their agent tokens. | diff --git a/apps/web/src/content/pages/docs/sdk/nodejs/reference.mdx b/apps/web/src/content/pages/docs/sdk/nodejs/reference.mdx index 3b8684a0..8d3bf35f 100644 --- a/apps/web/src/content/pages/docs/sdk/nodejs/reference.mdx +++ b/apps/web/src/content/pages/docs/sdk/nodejs/reference.mdx @@ -56,6 +56,23 @@ description: "Complete reference for enums, regions, assertions, and TypeScript | `MONITORING` | Fix deployed, monitoring | | `RESOLVED` | Issue fully resolved | +### IncidentSeverity + +| Value | Description | +|-------|-------------| +| `CRITICAL` | Core functionality is down for most users | +| `MAJOR` | Significant impact on part of the product | +| `MINOR` | Limited impact or a degraded experience | + +### IncidentStatus + +| Value | Description | +|-------|-------------| +| `OPEN` | The team is working on it | +| `MITIGATED` | Impact is contained, the fix is not final | +| `RESOLVED` | The incident is over | +| `CANCELED` | A false alarm; closes the incident | + ### OverallStatus | Value | Description | diff --git a/oxlint.config.ts b/oxlint.config.ts index 42c3a87c..03fc287c 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -66,6 +66,7 @@ export default defineConfig({ "apps/server/src/routes/rpc/handlers/status-report/**", "apps/server/src/routes/rpc/handlers/maintenance/**", "apps/server/src/routes/rpc/handlers/notification/**", + "apps/server/src/routes/rpc/handlers/incident/**", "apps/server/src/routes/slack/interactions.ts", ], excludeFiles: [ diff --git a/packages/api/src/router/incident.ts b/packages/api/src/router/incident.ts index b1cd02d3..61fd283d 100644 --- a/packages/api/src/router/incident.ts +++ b/packages/api/src/router/incident.ts @@ -14,26 +14,29 @@ import { SetIncidentStatusInput, UpdateIncidentInput, addIncidentNote, - allowedTransitions, approvePostmortem, draftPostmortem, generatePostmortemDraft, getPostmortem, - announceIncidentChange, - announceInChannel, + afterIncidentClosed, + afterIncidentDeclared, + afterIncidentDeleted, + afterIncidentStatusChanged, + afterIncidentUpdated, + afterPostmortemApproved, bindIncidentSlackChannel, closeIncident, declareIncident, deleteIncident, - displayName, escapeMrkdwn, getIncident, getIncidentForStatusReport, - isDeletable, + getIncidentOrThrow, linkIncidentStatusReport, listIncidentEvents, listIncidents, - openIncidentSlackChannel, + type IncidentEffects, + resolveDashboardUrl, setIncidentStatus, unbindIncidentSlackChannel, unlinkIncidentStatusReport, @@ -49,19 +52,17 @@ import { chatRateLimit } from "../lib/chat-rate-limit"; import { toServiceCtx, toTRPCError } from "../service-adapter"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -const DASHBOARD_URL = - process.env.NODE_ENV === "production" - ? "https://app.openstatus.dev" - : "http://localhost:3001"; - -const clientFor = (token: string) => new WebClient(token); +const DASHBOARD_URL = resolveDashboardUrl({ + nodeEnv: process.env.NODE_ENV, + override: process.env.DASHBOARD_URL, +}); type AuthedCtx = Parameters[0]; -/** Slack follow-ups run after the response; the incident is already saved. */ -function afterResponse(task: () => Promise) { +/** Follow-ups run after the response; the incident is already saved. */ +function afterResponse(task: () => Promise) { const run = () => - task().catch((err) => console.warn("incident slack follow-up failed", err)); + task().catch((err) => console.warn("incident follow-up failed", err)); try { after(run); } catch { @@ -69,53 +70,15 @@ function afterResponse(task: () => Promise) { } } -function announce( - ctx: AuthedCtx, - incidentId: number, - text: string, - archive = false, -) { - afterResponse(() => - announceIncidentChange({ - ctx: toServiceCtx(ctx), - incidentId, - text, - clientFor, - dashboardUrl: DASHBOARD_URL, - archive, - }), - ); -} - -function actorName(ctx: AuthedCtx): string { - return escapeMrkdwn(ctx.user.name || ctx.user.email || "A teammate"); -} - -function quote(note: string | undefined): string { - return note ? `\n>${escapeMrkdwn(note).replaceAll("\n", "\n>")}` : ""; -} - -/** Best effort: a failed email never fails the mutation that assigned them. */ -async function notifyCommander(ctx: AuthedCtx, incidentId: number) { - try { - const row = await getIncident({ - ctx: toServiceCtx(ctx), - input: { id: incidentId }, - }); - const commander = row?.commander; - if (!row || !commander?.email || commander.id === ctx.user.id) return; - await sendIncidentCommander({ - to: commander.email, - incidentTitle: row.title, - severity: row.severity, - workspaceName: ctx.workspace.name ?? ctx.workspace.slug, - assignedBy: actorName(ctx), - url: `${DASHBOARD_URL}/incidents/${row.id}`, - idempotencyKey: `incident-commander:${row.id}:${commander.id}:${row.updatedAt.getTime()}`, - }); - } catch (err) { - console.warn("incident commander email failed", { incidentId, err }); - } +function effectsFor(ctx: AuthedCtx): IncidentEffects { + const name = ctx.user.name || ctx.user.email || "A teammate"; + return { + clientFor: (token) => new WebClient(token), + dashboardUrl: DASHBOARD_URL, + sendCommanderEmail: sendIncidentCommander, + actorLabel: escapeMrkdwn(name), + assignedBy: escapeMrkdwn(name), + }; } export const incidentRouter = createTRPCRouter({ @@ -131,7 +94,7 @@ export const incidentRouter = createTRPCRouter({ ) .query(async ({ ctx, input }) => { try { - return await listIncidents({ ctx: toServiceCtx(ctx), input }); + return (await listIncidents({ ctx: toServiceCtx(ctx), input })).items; } catch (err) { toTRPCError(err); } @@ -141,18 +104,7 @@ export const incidentRouter = createTRPCRouter({ .input(IncidentIdInput) .query(async ({ ctx, input }) => { try { - const row = await getIncident({ ctx: toServiceCtx(ctx), input }); - if (!row) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Incident not found", - }); - } - return { - ...row, - allowedTransitions: allowedTransitions(row), - deletable: isDeletable(row), - }; + return await getIncidentOrThrow({ ctx: toServiceCtx(ctx), input }); } catch (err) { toTRPCError(err); } @@ -195,17 +147,14 @@ export const incidentRouter = createTRPCRouter({ ctx: toServiceCtx(ctx), input: declare, }); - if (row.commanderId !== null) await notifyCommander(ctx, row.id); - if (openSlackChannel) { - afterResponse(async () => { - await openIncidentSlackChannel({ - ctx: toServiceCtx(ctx), - incidentId: row.id, - clientFor, - dashboardUrl: DASHBOARD_URL, - }); - }); - } + afterResponse(() => + afterIncidentDeclared({ + ctx: toServiceCtx(ctx), + effects: effectsFor(ctx), + incident: row, + openSlackChannel: openSlackChannel ?? false, + }), + ); return row; } catch (err) { toTRPCError(err); @@ -222,42 +171,16 @@ export const incidentRouter = createTRPCRouter({ input: { id: input.id }, }); const row = await updateIncident({ ctx: toServiceCtx(ctx), input }); - const commanderChanged = row.commanderId !== before?.commanderId; - if (row.commanderId !== null && commanderChanged) { - await notifyCommander(ctx, row.id); - } - const changes: string[] = []; - if (before && row.title !== before.title) { - changes.push(`title is now *${escapeMrkdwn(row.title)}*`); - } - if (before && row.summary !== before.summary) { - changes.push( - row.summary ? "summary was updated" : "summary was removed", - ); - } - if (before && row.startedAt.getTime() !== before.startedAt.getTime()) { - const seconds = Math.floor(row.startedAt.getTime() / 1000); - changes.push( - `start time is now `, - ); - } - if (before && row.severity !== before.severity) { - changes.push(`severity is now *${row.severity}*`); - } - if (before && commanderChanged) { - const after = await getIncident({ - ctx: toServiceCtx(ctx), - input: { id: row.id }, - }); - changes.push( - after?.commander - ? `${escapeMrkdwn(displayName(after.commander))} is now commander` - : "there is no commander", + if (before) { + afterResponse(() => + afterIncidentUpdated({ + ctx: toServiceCtx(ctx), + effects: effectsFor(ctx), + before, + after: row, + }), ); } - if (changes.length) { - announce(ctx, row.id, `${actorName(ctx)}: ${changes.join(", ")}.`); - } return row; } catch (err) { toTRPCError(err); @@ -270,11 +193,14 @@ export const incidentRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { try { const row = await setIncidentStatus({ ctx: toServiceCtx(ctx), input }); - announce( - ctx, - row.id, - `${actorName(ctx)} marked the incident *${row.status}*.${quote(input.note)}`, - row.status === "canceled", + afterResponse(() => + afterIncidentStatusChanged({ + ctx: toServiceCtx(ctx), + effects: effectsFor(ctx), + incidentId: row.id, + status: row.status, + note: input.note, + }), ); return row; } catch (err) { @@ -409,15 +335,14 @@ export const incidentRouter = createTRPCRouter({ ? await getIncident({ ctx: toServiceCtx(ctx), input }) : undefined; const row = await approvePostmortem({ ctx: toServiceCtx(ctx), input }); - // Only announce (and archive) when this approval actually closed it. - if (before && !before.closedAt) { - announce( - ctx, - input.id, - `${actorName(ctx)} approved the postmortem and closed the incident.`, - true, - ); - } + afterResponse(() => + afterPostmortemApproved({ + ctx: toServiceCtx(ctx), + effects: effectsFor(ctx), + incidentId: input.id, + closed: !!before && !before.closedAt, + }), + ); return row; } catch (err) { toTRPCError(err); @@ -430,7 +355,13 @@ export const incidentRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { try { const row = await closeIncident({ ctx: toServiceCtx(ctx), input }); - announce(ctx, row.id, `${actorName(ctx)} closed the incident.`, true); + afterResponse(() => + afterIncidentClosed({ + ctx: toServiceCtx(ctx), + effects: effectsFor(ctx), + incidentId: row.id, + }), + ); return row; } catch (err) { toTRPCError(err); @@ -444,15 +375,12 @@ export const incidentRouter = createTRPCRouter({ try { const before = await getIncident({ ctx: toServiceCtx(ctx), input }); await deleteIncident({ ctx: toServiceCtx(ctx), input }); - if (before?.slackChannelId) { + if (before) { afterResponse(() => - announceInChannel({ + afterIncidentDeleted({ ctx: toServiceCtx(ctx), - incident: before, - text: `${actorName(ctx)} deleted this incident: it was declared by mistake.`, - clientFor, - dashboardUrl: DASHBOARD_URL, - archive: true, + effects: effectsFor(ctx), + before, }), ); } diff --git a/packages/proto/api/openstatus/incident/v1/incident.proto b/packages/proto/api/openstatus/incident/v1/incident.proto new file mode 100644 index 00000000..85f77aba --- /dev/null +++ b/packages/proto/api/openstatus/incident/v1/incident.proto @@ -0,0 +1,232 @@ +syntax = "proto3"; + +package openstatus.incident.v1; + +import "openstatus/status_report/v1/status_report.proto"; + +option go_package = "github.com/openstatushq/openstatus/packages/proto/openstatus/incident/v1;incidentv1"; + +// IncidentSeverity is how bad an incident is. +enum IncidentSeverity { + INCIDENT_SEVERITY_UNSPECIFIED = 0; + INCIDENT_SEVERITY_CRITICAL = 1; + INCIDENT_SEVERITY_MAJOR = 2; + INCIDENT_SEVERITY_MINOR = 3; +} + +// IncidentStatus is the lifecycle state of an incident. +// Closed is not a status: a closed incident has closed_at set and keeps its last status. +enum IncidentStatus { + INCIDENT_STATUS_UNSPECIFIED = 0; + INCIDENT_STATUS_OPEN = 1; + INCIDENT_STATUS_MITIGATED = 2; + INCIDENT_STATUS_RESOLVED = 3; + INCIDENT_STATUS_CANCELED = 4; +} + +// IncidentEventType is the kind of entry in an incident timeline. +enum IncidentEventType { + INCIDENT_EVENT_TYPE_UNSPECIFIED = 0; + INCIDENT_EVENT_TYPE_DECLARED = 1; + INCIDENT_EVENT_TYPE_SEVERITY_CHANGED = 2; + INCIDENT_EVENT_TYPE_STATUS_CHANGED = 3; + INCIDENT_EVENT_TYPE_COMMANDER_CHANGED = 4; + INCIDENT_EVENT_TYPE_STARTED_AT_CHANGED = 5; + INCIDENT_EVENT_TYPE_NOTE = 6; + INCIDENT_EVENT_TYPE_STATUS_REPORT_LINKED = 7; + INCIDENT_EVENT_TYPE_STATUS_REPORT_UNLINKED = 8; + INCIDENT_EVENT_TYPE_SLACK_CHANNEL_BOUND = 9; + INCIDENT_EVENT_TYPE_SLACK_CHANNEL_UNBOUND = 10; + INCIDENT_EVENT_TYPE_RESOLVED = 11; + INCIDENT_EVENT_TYPE_CANCELED = 12; + INCIDENT_EVENT_TYPE_POSTMORTEM_DRAFTED = 13; + INCIDENT_EVENT_TYPE_POSTMORTEM_UPDATED = 14; + INCIDENT_EVENT_TYPE_POSTMORTEM_APPROVED = 15; + INCIDENT_EVENT_TYPE_CLOSED = 16; +} + +// PostmortemStatus is the review state of a postmortem. +enum PostmortemStatus { + POSTMORTEM_STATUS_UNSPECIFIED = 0; + POSTMORTEM_STATUS_DRAFT = 1; + POSTMORTEM_STATUS_APPROVED = 2; +} + +// PostmortemAuthor is who wrote the current postmortem body. +enum PostmortemAuthor { + POSTMORTEM_AUTHOR_UNSPECIFIED = 0; + POSTMORTEM_AUTHOR_AGENT = 1; + POSTMORTEM_AUTHOR_USER = 2; +} + +// IncidentUser is a workspace member referenced by an incident. +message IncidentUser { + // Email address of the member (empty for a deleted account). + string email = 1; + + // Display name of the member ("Deleted user" for a deleted account). + string name = 2; +} + +// IncidentStatusReport is the public status report linked to an incident. +message IncidentStatusReport { + // ID of the status report. + string id = 1; + + // Title of the status report. + string title = 2; + + // Current status of the status report. + openstatus.status_report.v1.StatusReportStatus status = 3; + + // ID of the status page the report belongs to. + string page_id = 4; +} + +// IncidentEvent is one entry of an incident timeline. +message IncidentEvent { + // Unique identifier for the event. + string id = 1; + + // Kind of event. + IncidentEventType type = 2; + + // Text of the event: the note for notes, a rendered summary otherwise. + string message = 3; + + // Member who caused the event (unset for system events and API keys without a creator). + optional IncidentUser created_by = 4; + + // Timestamp when the event was recorded (RFC 3339 format). + string created_at = 5; +} + +// IncidentSummary is the metadata of an incident (used in list responses). +message IncidentSummary { + // Unique identifier for the incident. + string id = 1; + + // Title of the incident. + string title = 2; + + // Severity of the incident. + IncidentSeverity severity = 3; + + // Current status of the incident. + IncidentStatus status = 4; + + // Member leading the response (unset when unassigned). + optional IncidentUser commander = 5; + + // Timestamp when the incident was declared (RFC 3339 format). + string declared_at = 6; + + // Timestamp when the impact began (RFC 3339 format). + string started_at = 7; + + // Timestamp of the last resolution (RFC 3339 format). + optional string resolved_at = 8; + + // Timestamp when the incident was closed or canceled (RFC 3339 format). + optional string closed_at = 9; + + // Linked public status report. + optional IncidentStatusReport status_report = 10; + + // Timestamp when the incident was created (RFC 3339 format). + string created_at = 11; + + // Timestamp when the incident was last updated (RFC 3339 format). + string updated_at = 12; +} + +// Incident is a managed incident with full details. +message Incident { + // Unique identifier for the incident. + string id = 1; + + // Title of the incident. + string title = 2; + + // Severity of the incident. + IncidentSeverity severity = 3; + + // Current status of the incident. + IncidentStatus status = 4; + + // Short human summary. + optional string summary = 5; + + // Member leading the response (unset when unassigned). + optional IncidentUser commander = 6; + + // Member who declared the incident (unset for API keys without a creator). + optional IncidentUser declared_by = 7; + + // Member who last resolved the incident. + optional IncidentUser resolved_by = 8; + + // Timestamp when the incident was declared (RFC 3339 format). + string declared_at = 9; + + // Timestamp when the impact began (RFC 3339 format). + string started_at = 10; + + // Timestamp when the incident was first mitigated (RFC 3339 format). + optional string mitigated_at = 11; + + // Timestamp of the last resolution (RFC 3339 format). + optional string resolved_at = 12; + + // Timestamp when the incident was closed or canceled (RFC 3339 format). + // A closed incident is read-only except for its postmortem. + optional string closed_at = 13; + + // Linked public status report. + optional IncidentStatusReport status_report = 14; + + // Link to the bound Slack channel. + optional string slack_channel_url = 15; + + // Statuses SetIncidentStatus accepts from the current state (empty once closed). + repeated IncidentStatus allowed_transitions = 16; + + // Whether DeleteIncident is allowed: only while open and never mitigated, resolved or closed. + bool deletable = 17; + + // Timeline, newest first (only included in GetIncident). + repeated IncidentEvent events = 18; + + // Timestamp when the incident was created (RFC 3339 format). + string created_at = 19; + + // Timestamp when the incident was last updated (RFC 3339 format). + string updated_at = 20; +} + +// Postmortem is the review written after an incident is resolved. +message Postmortem { + // ID of the incident the postmortem belongs to. + string incident_id = 1; + + // Review state of the postmortem. + PostmortemStatus status = 2; + + // Markdown body of the postmortem. + string content = 3; + + // Who wrote the current body. + PostmortemAuthor drafted_by = 4; + + // Member who approved the postmortem. + optional IncidentUser approved_by = 5; + + // Timestamp when the postmortem was approved (RFC 3339 format). + optional string approved_at = 6; + + // Timestamp when the postmortem was created (RFC 3339 format). + string created_at = 7; + + // Timestamp when the postmortem was last updated (RFC 3339 format). + string updated_at = 8; +} diff --git a/packages/proto/api/openstatus/incident/v1/service.proto b/packages/proto/api/openstatus/incident/v1/service.proto new file mode 100644 index 00000000..c520c66a --- /dev/null +++ b/packages/proto/api/openstatus/incident/v1/service.proto @@ -0,0 +1,347 @@ +syntax = "proto3"; + +package openstatus.incident.v1; + +import "buf/validate/validate.proto"; +import "gnostic/openapi/v3/annotations.proto"; +import "openstatus/incident/v1/incident.proto"; + +option go_package = "github.com/openstatushq/openstatus/packages/proto/openstatus/incident/v1;incidentv1"; + +// IncidentService declares and runs incidents: lifecycle, timeline, status report link and postmortem. +service IncidentService { + // DeclareIncident declares a new incident. + rpc DeclareIncident(DeclareIncidentRequest) returns (DeclareIncidentResponse) { + option (gnostic.openapi.v3.operation) = { + description: "Declares a new incident in the open status. The commander is set by email and must be a member of the workspace; without one the incident is unassigned. A newly assigned commander is emailed unless they own the API key. When open_slack_channel is true and Slack is connected, a dedicated channel is created and the team invited. started_at defaults to now and can be set in the past for a retroactive declare." + }; + } + + // GetIncident retrieves an incident by ID, including its timeline. + rpc GetIncident(GetIncidentRequest) returns (GetIncidentResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + } + + // ListIncidents returns the incidents of the workspace (metadata only), open first. + rpc ListIncidents(ListIncidentsRequest) returns (ListIncidentsResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + } + + // UpdateIncident edits the title, severity, summary, commander or start time of an incident. + rpc UpdateIncident(UpdateIncidentRequest) returns (UpdateIncidentResponse); + + // SetIncidentStatus moves an incident through its lifecycle, optionally with a note. + rpc SetIncidentStatus(SetIncidentStatusRequest) returns (SetIncidentStatusResponse) { + option (gnostic.openapi.v3.operation) = { + description: "Moves an incident to a new status. Allowed transitions: open -> mitigated, resolved or canceled; mitigated -> resolved, open or canceled; resolved -> open. Canceled is terminal and closes the incident. The same status, a forbidden transition or a closed incident fail with failed_precondition; Incident.allowed_transitions lists what is valid. A linked status report is never updated: post the public update with StatusReportService.AddStatusReportUpdate." + }; + } + + // AddIncidentNote appends a note to the incident timeline. + rpc AddIncidentNote(AddIncidentNoteRequest) returns (AddIncidentNoteResponse); + + // LinkStatusReport links a public status report to the incident. + rpc LinkStatusReport(LinkStatusReportRequest) returns (LinkStatusReportResponse); + + // UnlinkStatusReport removes the link to the incident's status report. + rpc UnlinkStatusReport(UnlinkStatusReportRequest) returns (UnlinkStatusReportResponse); + + // CloseIncident closes a resolved incident. + rpc CloseIncident(CloseIncidentRequest) returns (CloseIncidentResponse) { + option (gnostic.openapi.v3.operation) = { + description: "Closes a resolved incident, after which only its postmortem can change. Requires an approved postmortem, or skip_postmortem set to true. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this." + }; + } + + // DeleteIncident removes an incident declared by mistake. + rpc DeleteIncident(DeleteIncidentRequest) returns (DeleteIncidentResponse) { + option (gnostic.openapi.v3.operation) = { + description: "Deletes an incident declared by mistake. Only allowed while the incident is open and was never mitigated, resolved or closed (Incident.deletable); cancel it otherwise. Allowed for workspace owners and admins only. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this." + }; + } + + // GetPostmortem retrieves the postmortem of an incident, if any. + rpc GetPostmortem(GetPostmortemRequest) returns (GetPostmortemResponse) { + option idempotency_level = NO_SIDE_EFFECTS; + } + + // UpdatePostmortem creates or replaces the postmortem body of a resolved incident. + rpc UpdatePostmortem(UpdatePostmortemRequest) returns (UpdatePostmortemResponse); + + // ApprovePostmortem approves the postmortem and optionally closes the incident. + rpc ApprovePostmortem(ApprovePostmortemRequest) returns (ApprovePostmortemResponse) { + option (gnostic.openapi.v3.operation) = { + description: "Approves the draft postmortem of a resolved incident. With close set to true the incident is closed in the same step. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this." + }; + } +} + +// DeclareIncidentRequest is the request to declare a new incident. +message DeclareIncidentRequest { + // Title of the incident (required, 1-256 characters). + string title = 1 [ + (buf.validate.field).string = { + min_len: 1 + max_len: 256 + }, + (gnostic.openapi.v3.property) = {example: {yaml: "Checkout API returns 502"}} + ]; + + // Severity of the incident (required). + IncidentSeverity severity = 2 [(buf.validate.field).enum = { + defined_only: true + not_in: [0] + }]; + + // Short human summary (optional, up to 4000 characters). + optional string summary = 3 [(buf.validate.field).string.max_len = 4000]; + + // Email of the member who leads the response (optional, defaults to unassigned). + optional string commander_email = 4 [ + (buf.validate.field).string.email = true, + (gnostic.openapi.v3.property) = {example: {yaml: "jane@example.com"}} + ]; + + // When the impact began (RFC 3339 format, optional, defaults to now). + optional string started_at = 5 [ + (buf.validate.field).string.pattern = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$", + (gnostic.openapi.v3.property) = {example: {yaml: "\"2024-03-15T10:30:00Z\""}} + ]; + + // ID of a status report to link (optional). + optional string status_report_id = 6 [(buf.validate.field).string.min_len = 1]; + + // Whether to open a Slack channel for the incident when Slack is connected (optional, defaults to false). + optional bool open_slack_channel = 7; +} + +// DeclareIncidentResponse is the response after declaring an incident. +message DeclareIncidentResponse { + // The declared incident. + Incident incident = 1; +} + +// GetIncidentRequest is the request to get an incident by ID. +message GetIncidentRequest { + // ID of the incident to retrieve (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; +} + +// GetIncidentResponse is the response containing the incident and its timeline. +message GetIncidentResponse { + // The requested incident. + Incident incident = 1; +} + +// ListIncidentsRequest is the request to list incidents. +message ListIncidentsRequest { + // Maximum number of incidents to return (1-100, defaults to 50). + optional int32 limit = 1 [(buf.validate.field).int32 = { + gte: 1 + lte: 100 + }]; + + // Number of incidents to skip for pagination (defaults to 0). + optional int32 offset = 2 [(buf.validate.field).int32.gte = 0]; + + // Filter by status (optional). If empty, returns all statuses. + repeated IncidentStatus statuses = 3 [(buf.validate.field).repeated.items.enum = { + defined_only: true + not_in: [0] + }]; + + // Filter by closed state (optional). If unset, returns both. + optional bool closed = 4; +} + +// ListIncidentsResponse is the response containing incident summaries. +message ListIncidentsResponse { + // List of incidents (metadata only, use GetIncident for full details). + repeated IncidentSummary incidents = 1; + + // Total number of incidents matching the filter. + int32 total_size = 2; +} + +// UpdateIncidentRequest is the request to edit an incident. +message UpdateIncidentRequest { + // ID of the incident to update (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; + + // New title (optional, 1-256 characters). + optional string title = 2 [(buf.validate.field).string = { + min_len: 1 + max_len: 256 + }]; + + // New severity (optional). + optional IncidentSeverity severity = 3 [(buf.validate.field).enum = { + defined_only: true + not_in: [0] + }]; + + // New summary (optional, 1-4000 characters). + optional string summary = 4 [(buf.validate.field).string = { + min_len: 1 + max_len: 4000 + }]; + + // Set to true to remove the summary. Cannot be combined with summary. + optional bool clear_summary = 5; + + // Email of the new commander (optional). + optional string commander_email = 6 [(buf.validate.field).string.email = true]; + + // Set to true to unassign the commander. Cannot be combined with commander_email. + optional bool clear_commander = 7; + + // New start of impact (RFC 3339 format, optional). + optional string started_at = 8 [(buf.validate.field).string.pattern = "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$"]; +} + +// UpdateIncidentResponse is the response after updating an incident. +message UpdateIncidentResponse { + // The updated incident (without its timeline). + Incident incident = 1; +} + +// SetIncidentStatusRequest is the request to change the status of an incident. +message SetIncidentStatusRequest { + // ID of the incident (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; + + // Target status (required). + IncidentStatus status = 2 [(buf.validate.field).enum = { + defined_only: true + not_in: [0] + }]; + + // Note recorded with the change (optional, up to 10000 characters). + optional string note = 3 [(buf.validate.field).string.max_len = 10000]; +} + +// SetIncidentStatusResponse is the response after changing the status of an incident. +message SetIncidentStatusResponse { + // The updated incident (without its timeline). + Incident incident = 1; +} + +// AddIncidentNoteRequest is the request to add a note to an incident timeline. +message AddIncidentNoteRequest { + // ID of the incident (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; + + // Note text, markdown (required, 1-10000 characters). + string message = 2 [(buf.validate.field).string = { + min_len: 1 + max_len: 10000 + }]; +} + +// AddIncidentNoteResponse is the response after adding a note. +message AddIncidentNoteResponse { + // The timeline event that was added. + IncidentEvent event = 1; +} + +// LinkStatusReportRequest is the request to link a status report to an incident. +message LinkStatusReportRequest { + // ID of the incident (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; + + // ID of the status report to link (required). + string status_report_id = 2 [(buf.validate.field).string.min_len = 1]; +} + +// LinkStatusReportResponse is the response after linking a status report. +message LinkStatusReportResponse { + // The updated incident (without its timeline). + Incident incident = 1; +} + +// UnlinkStatusReportRequest is the request to unlink the status report of an incident. +message UnlinkStatusReportRequest { + // ID of the incident (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; +} + +// UnlinkStatusReportResponse is the response after unlinking the status report. +message UnlinkStatusReportResponse { + // The updated incident (without its timeline). + Incident incident = 1; +} + +// CloseIncidentRequest is the request to close a resolved incident. +message CloseIncidentRequest { + // ID of the incident (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; + + // Close without an approved postmortem (optional, defaults to false). + optional bool skip_postmortem = 2; +} + +// CloseIncidentResponse is the response after closing an incident. +message CloseIncidentResponse { + // The closed incident (without its timeline). + Incident incident = 1; +} + +// DeleteIncidentRequest is the request to delete an incident. +message DeleteIncidentRequest { + // ID of the incident (required). + string id = 1 [(buf.validate.field).string.min_len = 1]; +} + +// DeleteIncidentResponse is the response after deleting an incident. +message DeleteIncidentResponse { + // Whether the deletion was successful. + bool success = 1; +} + +// GetPostmortemRequest is the request to get the postmortem of an incident. +message GetPostmortemRequest { + // ID of the incident (required). + string incident_id = 1 [(buf.validate.field).string.min_len = 1]; +} + +// GetPostmortemResponse is the response containing the postmortem, if any. +message GetPostmortemResponse { + // The postmortem (unset when none has been written yet). + optional Postmortem postmortem = 1; +} + +// UpdatePostmortemRequest is the request to write the postmortem of a resolved incident. +message UpdatePostmortemRequest { + // ID of the incident (required). + string incident_id = 1 [(buf.validate.field).string.min_len = 1]; + + // Markdown body (required, 1-100000 characters). Replaces the current body; an approved postmortem stays approved. + string content = 2 [(buf.validate.field).string = { + min_len: 1 + max_len: 100000 + }]; +} + +// UpdatePostmortemResponse is the response after writing the postmortem. +message UpdatePostmortemResponse { + // The saved postmortem. + Postmortem postmortem = 1; +} + +// ApprovePostmortemRequest is the request to approve the postmortem of an incident. +message ApprovePostmortemRequest { + // ID of the incident (required). + string incident_id = 1 [(buf.validate.field).string.min_len = 1]; + + // Also close the incident (optional, defaults to false). + optional bool close = 2; +} + +// ApprovePostmortemResponse is the response after approving the postmortem. +message ApprovePostmortemResponse { + // The approved postmortem. + Postmortem postmortem = 1; + + // The incident after approval (without its timeline). + Incident incident = 2; +} diff --git a/packages/proto/api/openstatus/status_report/v1/service.proto b/packages/proto/api/openstatus/status_report/v1/service.proto index de6a1c8e..c4329ca3 100644 --- a/packages/proto/api/openstatus/status_report/v1/service.proto +++ b/packages/proto/api/openstatus/status_report/v1/service.proto @@ -77,6 +77,10 @@ message CreateStatusReportRequest { // the named components are added to the report's affected set. Omitting this // field creates a legacy report without impact tracking. repeated ComponentImpact component_impacts = 8; + + // ID of an incident to link the report to (optional). Linked in the same + // step: a closed incident, or one already linked to a report, fails the create. + optional string incident_id = 9 [(buf.validate.field).string.min_len = 1]; } // CreateStatusReportResponse is the response after creating a status report. diff --git a/packages/proto/api/openstatus/status_report/v1/status_report.proto b/packages/proto/api/openstatus/status_report/v1/status_report.proto index 65b59389..5058c1fa 100644 --- a/packages/proto/api/openstatus/status_report/v1/status_report.proto +++ b/packages/proto/api/openstatus/status_report/v1/status_report.proto @@ -72,6 +72,9 @@ message StatusReportSummary { // Timestamp when the report was last updated (RFC 3339 format). string updated_at = 6; + + // ID of the incident this report communicates (unset when not linked). + optional string incident_id = 7; } // StatusReport represents an incident or maintenance report with full details. @@ -96,4 +99,7 @@ message StatusReport { // Timestamp when the report was last updated (RFC 3339 format). string updated_at = 7; + + // ID of the incident this report communicates (unset when not linked). + optional string incident_id = 8; } diff --git a/packages/proto/base.openapi.yaml b/packages/proto/base.openapi.yaml index b3a2bd9f..f05bad87 100644 --- a/packages/proto/base.openapi.yaml +++ b/packages/proto/base.openapi.yaml @@ -39,8 +39,12 @@ tags: and associate them with monitors. Supports 12 notification providers. - name: StatusReportService description: | - Create and manage incident reports with status updates. Reports follow a lifecycle: + Create and manage public status reports with status updates. Reports follow a lifecycle: investigating -> identified -> monitoring -> resolved. + - name: IncidentService + description: | + Declare and run incidents: lifecycle (open -> mitigated -> resolved, or canceled), + timeline notes, a linked status report, and the postmortem. - name: MaintenanceService description: | Schedule maintenance windows for status page components. Subscribers can be diff --git a/packages/proto/gen/openapi.yaml b/packages/proto/gen/openapi.yaml index 6ae80571..db8a09e0 100644 --- a/packages/proto/gen/openapi.yaml +++ b/packages/proto/gen/openapi.yaml @@ -118,250 +118,704 @@ components: - SERVING_STATUS_SERVING - SERVING_STATUS_NOT_SERVING description: ServingStatus represents the health status of the service. - openstatus.maintenance.v1.CreateMaintenanceRequest: + openstatus.incident.v1.AddIncidentNoteRequest: type: object properties: - title: + id: type: string - examples: - - Database Migration - title: title - maxLength: 256 + title: id minLength: 1 - description: Title of the maintenance (required, 1-256 characters). + description: ID of the incident (required). message: type: string title: message + maxLength: 10000 minLength: 1 - description: Message describing the maintenance (required). - from: + description: Note text, markdown (required, 1-10000 characters). + title: AddIncidentNoteRequest + additionalProperties: false + description: AddIncidentNoteRequest is the request to add a note to an incident timeline. + openstatus.incident.v1.AddIncidentNoteResponse: + type: object + properties: + event: + title: event + description: The timeline event that was added. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentEvent' + title: AddIncidentNoteResponse + additionalProperties: false + description: AddIncidentNoteResponse is the response after adding a note. + openstatus.incident.v1.ApprovePostmortemRequest: + type: object + properties: + incidentId: type: string - examples: - - "2024-03-01T02:00:00Z" - title: from - pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: Start time of the maintenance window (RFC 3339 format, required). - to: + title: incident_id + minLength: 1 + description: ID of the incident (required). + close: + type: + - boolean + - "null" + title: close + description: Also close the incident (optional, defaults to false). + title: ApprovePostmortemRequest + additionalProperties: false + description: ApprovePostmortemRequest is the request to approve the postmortem of an incident. + openstatus.incident.v1.ApprovePostmortemResponse: + type: object + properties: + postmortem: + title: postmortem + description: The approved postmortem. + $ref: '#/components/schemas/openstatus.incident.v1.Postmortem' + incident: + title: incident + description: The incident after approval (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: ApprovePostmortemResponse + additionalProperties: false + description: ApprovePostmortemResponse is the response after approving the postmortem. + openstatus.incident.v1.CloseIncidentRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident (required). + skipPostmortem: + type: + - boolean + - "null" + title: skip_postmortem + description: Close without an approved postmortem (optional, defaults to false). + title: CloseIncidentRequest + additionalProperties: false + description: CloseIncidentRequest is the request to close a resolved incident. + openstatus.incident.v1.CloseIncidentResponse: + type: object + properties: + incident: + title: incident + description: The closed incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: CloseIncidentResponse + additionalProperties: false + description: CloseIncidentResponse is the response after closing an incident. + openstatus.incident.v1.DeclareIncidentRequest: + type: object + properties: + title: type: string examples: - - "2024-03-01T06:00:00Z" - title: to + - Checkout API returns 502 + title: title + maxLength: 256 + minLength: 1 + description: Title of the incident (required, 1-256 characters). + severity: + not: + enum: + - INCIDENT_SEVERITY_UNSPECIFIED + title: severity + description: Severity of the incident (required). + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + summary: + type: + - string + - "null" + title: summary + maxLength: 4000 + description: Short human summary (optional, up to 4000 characters). + commanderEmail: + type: + - string + - "null" + examples: + - jane@example.com + title: commander_email + format: email + description: Email of the member who leads the response (optional, defaults to unassigned). + startedAt: + type: + - string + - "null" + examples: + - "2024-03-15T10:30:00Z" + title: started_at pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: End time of the maintenance window (RFC 3339 format, required). - pageId: - type: string - title: page_id + description: When the impact began (RFC 3339 format, optional, defaults to now). + statusReportId: + type: + - string + - "null" + title: status_report_id minLength: 1 - description: Page ID to associate with this maintenance (required). - pageComponentIds: - type: array - items: - type: string - title: page_component_ids - description: Page component IDs to associate with this maintenance (optional). - notify: + description: ID of a status report to link (optional). + openSlackChannel: type: - boolean - "null" - title: notify - description: Whether to notify subscribers about this maintenance (optional, defaults to false). - title: CreateMaintenanceRequest + title: open_slack_channel + description: Whether to open a Slack channel for the incident when Slack is connected (optional, defaults to false). + title: DeclareIncidentRequest additionalProperties: false - description: CreateMaintenanceRequest is the request to create a new maintenance window. - openstatus.maintenance.v1.CreateMaintenanceResponse: + description: DeclareIncidentRequest is the request to declare a new incident. + openstatus.incident.v1.DeclareIncidentResponse: type: object properties: - maintenance: - title: maintenance - description: The created maintenance. - $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' - title: CreateMaintenanceResponse + incident: + title: incident + description: The declared incident. + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: DeclareIncidentResponse additionalProperties: false - description: CreateMaintenanceResponse is the response after creating a maintenance window. - openstatus.maintenance.v1.DeleteMaintenanceRequest: + description: DeclareIncidentResponse is the response after declaring an incident. + openstatus.incident.v1.DeleteIncidentRequest: type: object properties: id: type: string title: id minLength: 1 - description: ID of the maintenance to delete (required). - title: DeleteMaintenanceRequest + description: ID of the incident (required). + title: DeleteIncidentRequest additionalProperties: false - description: DeleteMaintenanceRequest is the request to delete a maintenance window. - openstatus.maintenance.v1.DeleteMaintenanceResponse: + description: DeleteIncidentRequest is the request to delete an incident. + openstatus.incident.v1.DeleteIncidentResponse: type: object properties: success: type: boolean title: success description: Whether the deletion was successful. - title: DeleteMaintenanceResponse + title: DeleteIncidentResponse additionalProperties: false - description: DeleteMaintenanceResponse is the response after deleting a maintenance window. - openstatus.maintenance.v1.GetMaintenanceRequest: + description: DeleteIncidentResponse is the response after deleting an incident. + openstatus.incident.v1.GetIncidentRequest: type: object properties: id: type: string title: id minLength: 1 - description: ID of the maintenance to retrieve (required). - title: GetMaintenanceRequest + description: ID of the incident to retrieve (required). + title: GetIncidentRequest additionalProperties: false - description: GetMaintenanceRequest is the request to get a maintenance window by ID. - openstatus.maintenance.v1.GetMaintenanceResponse: + description: GetIncidentRequest is the request to get an incident by ID. + openstatus.incident.v1.GetIncidentResponse: type: object properties: - maintenance: - title: maintenance - description: The requested maintenance. - $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' - title: GetMaintenanceResponse + incident: + title: incident + description: The requested incident. + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: GetIncidentResponse additionalProperties: false - description: GetMaintenanceResponse is the response containing the maintenance window. - openstatus.maintenance.v1.ListMaintenancesRequest: + description: GetIncidentResponse is the response containing the incident and its timeline. + openstatus.incident.v1.GetPostmortemRequest: type: object properties: - limit: - type: - - integer - - "null" - title: limit - maximum: 100 - minimum: 1 - format: int32 - description: Maximum number of maintenances to return (1-100, defaults to 50). - offset: - type: - - integer - - "null" - title: offset - minimum: 0 - format: int32 - description: Number of maintenances to skip for pagination (defaults to 0). - pageId: - type: - - string - - "null" - title: page_id - description: Filter by page ID (optional). - title: ListMaintenancesRequest + incidentId: + type: string + title: incident_id + minLength: 1 + description: ID of the incident (required). + title: GetPostmortemRequest additionalProperties: false - description: ListMaintenancesRequest is the request to list maintenance windows. - openstatus.maintenance.v1.ListMaintenancesResponse: + description: GetPostmortemRequest is the request to get the postmortem of an incident. + openstatus.incident.v1.GetPostmortemResponse: type: object properties: - maintenances: - type: array - items: - $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary' - title: maintenances - description: List of maintenances. - totalSize: - type: integer - title: total_size - format: int32 - description: Total number of maintenances matching the filter. - title: ListMaintenancesResponse + postmortem: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.Postmortem' + - type: "null" + title: postmortem + description: The postmortem (unset when none has been written yet). + title: GetPostmortemResponse additionalProperties: false - description: ListMaintenancesResponse is the response containing maintenance window summaries. - openstatus.maintenance.v1.Maintenance: + description: GetPostmortemResponse is the response containing the postmortem, if any. + openstatus.incident.v1.Incident: type: object properties: id: type: string title: id - description: Unique identifier for the maintenance. + description: Unique identifier for the incident. title: type: string title: title - description: Title of the maintenance. - message: - type: string - title: message - description: Message describing the maintenance. - from: - type: string - title: from - description: Start time of the maintenance window (RFC 3339 format). - to: + description: Title of the incident. + severity: + title: severity + description: Severity of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + status: + title: status + description: Current status of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + summary: + type: + - string + - "null" + title: summary + description: Short human summary. + commander: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: commander + description: Member leading the response (unset when unassigned). + declaredBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: declared_by + description: Member who declared the incident (unset for API keys without a creator). + resolvedBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: resolved_by + description: Member who last resolved the incident. + declaredAt: type: string - title: to - description: End time of the maintenance window (RFC 3339 format). - pageId: + title: declared_at + description: Timestamp when the incident was declared (RFC 3339 format). + startedAt: type: string - title: page_id - description: ID of the page this maintenance is associated with. - pageComponentIds: + title: started_at + description: Timestamp when the impact began (RFC 3339 format). + mitigatedAt: + type: + - string + - "null" + title: mitigated_at + description: Timestamp when the incident was first mitigated (RFC 3339 format). + resolvedAt: + type: + - string + - "null" + title: resolved_at + description: Timestamp of the last resolution (RFC 3339 format). + closedAt: + type: + - string + - "null" + title: closed_at + description: |- + Timestamp when the incident was closed or canceled (RFC 3339 format). + A closed incident is read-only except for its postmortem. + statusReport: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatusReport' + - type: "null" + title: status_report + description: Linked public status report. + slackChannelUrl: + type: + - string + - "null" + title: slack_channel_url + description: Link to the bound Slack channel. + allowedTransitions: type: array items: - type: string - title: page_component_ids - description: IDs of affected page components. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + title: allowed_transitions + description: Statuses SetIncidentStatus accepts from the current state (empty once closed). + deletable: + type: boolean + title: deletable + description: 'Whether DeleteIncident is allowed: only while open and never mitigated, resolved or closed.' + events: + type: array + items: + $ref: '#/components/schemas/openstatus.incident.v1.IncidentEvent' + title: events + description: Timeline, newest first (only included in GetIncident). createdAt: type: string title: created_at - description: Timestamp when the maintenance was created (RFC 3339 format). + description: Timestamp when the incident was created (RFC 3339 format). updatedAt: type: string title: updated_at - description: Timestamp when the maintenance was last updated (RFC 3339 format). - title: Maintenance + description: Timestamp when the incident was last updated (RFC 3339 format). + title: Incident additionalProperties: false - description: Maintenance represents a maintenance window with full details. - openstatus.maintenance.v1.MaintenanceSummary: + description: Incident is a managed incident with full details. + openstatus.incident.v1.IncidentEvent: type: object properties: id: type: string title: id - description: Unique identifier for the maintenance. - title: - type: string - title: title - description: Title of the maintenance. + description: Unique identifier for the event. + type: + title: type + description: Kind of event. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentEventType' message: type: string title: message - description: Message describing the maintenance. - from: + description: 'Text of the event: the note for notes, a rendered summary otherwise.' + createdBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: created_by + description: Member who caused the event (unset for system events and API keys without a creator). + createdAt: type: string - title: from - description: Start time of the maintenance window (RFC 3339 format). - to: + title: created_at + description: Timestamp when the event was recorded (RFC 3339 format). + title: IncidentEvent + additionalProperties: false + description: IncidentEvent is one entry of an incident timeline. + openstatus.incident.v1.IncidentEventType: + type: string + title: IncidentEventType + enum: + - INCIDENT_EVENT_TYPE_UNSPECIFIED + - INCIDENT_EVENT_TYPE_DECLARED + - INCIDENT_EVENT_TYPE_SEVERITY_CHANGED + - INCIDENT_EVENT_TYPE_STATUS_CHANGED + - INCIDENT_EVENT_TYPE_COMMANDER_CHANGED + - INCIDENT_EVENT_TYPE_STARTED_AT_CHANGED + - INCIDENT_EVENT_TYPE_NOTE + - INCIDENT_EVENT_TYPE_STATUS_REPORT_LINKED + - INCIDENT_EVENT_TYPE_STATUS_REPORT_UNLINKED + - INCIDENT_EVENT_TYPE_SLACK_CHANNEL_BOUND + - INCIDENT_EVENT_TYPE_SLACK_CHANNEL_UNBOUND + - INCIDENT_EVENT_TYPE_RESOLVED + - INCIDENT_EVENT_TYPE_CANCELED + - INCIDENT_EVENT_TYPE_POSTMORTEM_DRAFTED + - INCIDENT_EVENT_TYPE_POSTMORTEM_UPDATED + - INCIDENT_EVENT_TYPE_POSTMORTEM_APPROVED + - INCIDENT_EVENT_TYPE_CLOSED + description: IncidentEventType is the kind of entry in an incident timeline. + openstatus.incident.v1.IncidentSeverity: + type: string + title: IncidentSeverity + enum: + - INCIDENT_SEVERITY_UNSPECIFIED + - INCIDENT_SEVERITY_CRITICAL + - INCIDENT_SEVERITY_MAJOR + - INCIDENT_SEVERITY_MINOR + description: IncidentSeverity is how bad an incident is. + openstatus.incident.v1.IncidentStatus: + type: string + title: IncidentStatus + enum: + - INCIDENT_STATUS_UNSPECIFIED + - INCIDENT_STATUS_OPEN + - INCIDENT_STATUS_MITIGATED + - INCIDENT_STATUS_RESOLVED + - INCIDENT_STATUS_CANCELED + description: |- + IncidentStatus is the lifecycle state of an incident. + Closed is not a status: a closed incident has closed_at set and keeps its last status. + openstatus.incident.v1.IncidentStatusReport: + type: object + properties: + id: type: string - title: to - description: End time of the maintenance window (RFC 3339 format). + title: id + description: ID of the status report. + title: + type: string + title: title + description: Title of the status report. + status: + title: status + description: Current status of the status report. + $ref: '#/components/schemas/openstatus.status_report.v1.StatusReportStatus' pageId: type: string title: page_id - description: ID of the page this maintenance is associated with. - pageComponentIds: + description: ID of the status page the report belongs to. + title: IncidentStatusReport + additionalProperties: false + description: IncidentStatusReport is the public status report linked to an incident. + openstatus.incident.v1.IncidentSummary: + type: object + properties: + id: + type: string + title: id + description: Unique identifier for the incident. + title: + type: string + title: title + description: Title of the incident. + severity: + title: severity + description: Severity of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + status: + title: status + description: Current status of the incident. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + commander: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: commander + description: Member leading the response (unset when unassigned). + declaredAt: + type: string + title: declared_at + description: Timestamp when the incident was declared (RFC 3339 format). + startedAt: + type: string + title: started_at + description: Timestamp when the impact began (RFC 3339 format). + resolvedAt: + type: + - string + - "null" + title: resolved_at + description: Timestamp of the last resolution (RFC 3339 format). + closedAt: + type: + - string + - "null" + title: closed_at + description: Timestamp when the incident was closed or canceled (RFC 3339 format). + statusReport: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatusReport' + - type: "null" + title: status_report + description: Linked public status report. + createdAt: + type: string + title: created_at + description: Timestamp when the incident was created (RFC 3339 format). + updatedAt: + type: string + title: updated_at + description: Timestamp when the incident was last updated (RFC 3339 format). + title: IncidentSummary + additionalProperties: false + description: IncidentSummary is the metadata of an incident (used in list responses). + openstatus.incident.v1.IncidentUser: + type: object + properties: + email: + type: string + title: email + description: Email address of the member (empty for a deleted account). + name: + type: string + title: name + description: Display name of the member ("Deleted user" for a deleted account). + title: IncidentUser + additionalProperties: false + description: IncidentUser is a workspace member referenced by an incident. + openstatus.incident.v1.LinkStatusReportRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident (required). + statusReportId: + type: string + title: status_report_id + minLength: 1 + description: ID of the status report to link (required). + title: LinkStatusReportRequest + additionalProperties: false + description: LinkStatusReportRequest is the request to link a status report to an incident. + openstatus.incident.v1.LinkStatusReportResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: LinkStatusReportResponse + additionalProperties: false + description: LinkStatusReportResponse is the response after linking a status report. + openstatus.incident.v1.ListIncidentsRequest: + type: object + properties: + limit: + type: + - integer + - "null" + title: limit + maximum: 100 + minimum: 1 + format: int32 + description: Maximum number of incidents to return (1-100, defaults to 50). + offset: + type: + - integer + - "null" + title: offset + minimum: 0 + format: int32 + description: Number of incidents to skip for pagination (defaults to 0). + statuses: type: array items: - type: string - title: page_component_ids - description: IDs of affected page components. + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + title: statuses + description: Filter by status (optional). If empty, returns all statuses. + closed: + type: + - boolean + - "null" + title: closed + description: Filter by closed state (optional). If unset, returns both. + title: ListIncidentsRequest + additionalProperties: false + description: ListIncidentsRequest is the request to list incidents. + openstatus.incident.v1.ListIncidentsResponse: + type: object + properties: + incidents: + type: array + items: + $ref: '#/components/schemas/openstatus.incident.v1.IncidentSummary' + title: incidents + description: List of incidents (metadata only, use GetIncident for full details). + totalSize: + type: integer + title: total_size + format: int32 + description: Total number of incidents matching the filter. + title: ListIncidentsResponse + additionalProperties: false + description: ListIncidentsResponse is the response containing incident summaries. + openstatus.incident.v1.Postmortem: + type: object + properties: + incidentId: + type: string + title: incident_id + description: ID of the incident the postmortem belongs to. + status: + title: status + description: Review state of the postmortem. + $ref: '#/components/schemas/openstatus.incident.v1.PostmortemStatus' + content: + type: string + title: content + description: Markdown body of the postmortem. + draftedBy: + title: drafted_by + description: Who wrote the current body. + $ref: '#/components/schemas/openstatus.incident.v1.PostmortemAuthor' + approvedBy: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentUser' + - type: "null" + title: approved_by + description: Member who approved the postmortem. + approvedAt: + type: + - string + - "null" + title: approved_at + description: Timestamp when the postmortem was approved (RFC 3339 format). createdAt: type: string title: created_at - description: Timestamp when the maintenance was created (RFC 3339 format). + description: Timestamp when the postmortem was created (RFC 3339 format). updatedAt: type: string title: updated_at - description: Timestamp when the maintenance was last updated (RFC 3339 format). - title: MaintenanceSummary + description: Timestamp when the postmortem was last updated (RFC 3339 format). + title: Postmortem additionalProperties: false - description: MaintenanceSummary represents metadata for a maintenance window (used in list responses). - openstatus.maintenance.v1.UpdateMaintenanceRequest: + description: Postmortem is the review written after an incident is resolved. + openstatus.incident.v1.PostmortemAuthor: + type: string + title: PostmortemAuthor + enum: + - POSTMORTEM_AUTHOR_UNSPECIFIED + - POSTMORTEM_AUTHOR_AGENT + - POSTMORTEM_AUTHOR_USER + description: PostmortemAuthor is who wrote the current postmortem body. + openstatus.incident.v1.PostmortemStatus: + type: string + title: PostmortemStatus + enum: + - POSTMORTEM_STATUS_UNSPECIFIED + - POSTMORTEM_STATUS_DRAFT + - POSTMORTEM_STATUS_APPROVED + description: PostmortemStatus is the review state of a postmortem. + openstatus.incident.v1.SetIncidentStatusRequest: type: object properties: id: type: string title: id minLength: 1 - description: ID of the maintenance to update (required). + description: ID of the incident (required). + status: + not: + enum: + - INCIDENT_STATUS_UNSPECIFIED + title: status + description: Target status (required). + $ref: '#/components/schemas/openstatus.incident.v1.IncidentStatus' + note: + type: + - string + - "null" + title: note + maxLength: 10000 + description: Note recorded with the change (optional, up to 10000 characters). + title: SetIncidentStatusRequest + additionalProperties: false + description: SetIncidentStatusRequest is the request to change the status of an incident. + openstatus.incident.v1.SetIncidentStatusResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: SetIncidentStatusResponse + additionalProperties: false + description: SetIncidentStatusResponse is the response after changing the status of an incident. + openstatus.incident.v1.UnlinkStatusReportRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident (required). + title: UnlinkStatusReportRequest + additionalProperties: false + description: UnlinkStatusReportRequest is the request to unlink the status report of an incident. + openstatus.incident.v1.UnlinkStatusReportResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: UnlinkStatusReportResponse + additionalProperties: false + description: UnlinkStatusReportResponse is the response after unlinking the status report. + openstatus.incident.v1.UpdateIncidentRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the incident to update (required). title: type: - string @@ -369,79 +823,414 @@ components: title: title maxLength: 256 minLength: 1 - description: New title for the maintenance (optional). - message: + description: New title (optional, 1-256 characters). + severity: + oneOf: + - $ref: '#/components/schemas/openstatus.incident.v1.IncidentSeverity' + - type: "null" + not: + enum: + - INCIDENT_SEVERITY_UNSPECIFIED + title: severity + description: New severity (optional). + summary: type: - string - "null" - title: message - description: New message for the maintenance (optional). - from: + title: summary + maxLength: 4000 + minLength: 1 + description: New summary (optional, 1-4000 characters). + clearSummary: + type: + - boolean + - "null" + title: clear_summary + description: Set to true to remove the summary. Cannot be combined with summary. + commanderEmail: type: - string - "null" - title: from - pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: New start time (RFC 3339 format, optional). - to: + title: commander_email + format: email + description: Email of the new commander (optional). + clearCommander: + type: + - boolean + - "null" + title: clear_commander + description: Set to true to unassign the commander. Cannot be combined with commander_email. + startedAt: type: - string - "null" + title: started_at + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: New start of impact (RFC 3339 format, optional). + title: UpdateIncidentRequest + additionalProperties: false + description: UpdateIncidentRequest is the request to edit an incident. + openstatus.incident.v1.UpdateIncidentResponse: + type: object + properties: + incident: + title: incident + description: The updated incident (without its timeline). + $ref: '#/components/schemas/openstatus.incident.v1.Incident' + title: UpdateIncidentResponse + additionalProperties: false + description: UpdateIncidentResponse is the response after updating an incident. + openstatus.incident.v1.UpdatePostmortemRequest: + type: object + properties: + incidentId: + type: string + title: incident_id + minLength: 1 + description: ID of the incident (required). + content: + type: string + title: content + maxLength: 100000 + minLength: 1 + description: Markdown body (required, 1-100000 characters). Replaces the current body; an approved postmortem stays approved. + title: UpdatePostmortemRequest + additionalProperties: false + description: UpdatePostmortemRequest is the request to write the postmortem of a resolved incident. + openstatus.incident.v1.UpdatePostmortemResponse: + type: object + properties: + postmortem: + title: postmortem + description: The saved postmortem. + $ref: '#/components/schemas/openstatus.incident.v1.Postmortem' + title: UpdatePostmortemResponse + additionalProperties: false + description: UpdatePostmortemResponse is the response after writing the postmortem. + openstatus.maintenance.v1.CreateMaintenanceRequest: + type: object + properties: + title: + type: string + examples: + - Database Migration + title: title + maxLength: 256 + minLength: 1 + description: Title of the maintenance (required, 1-256 characters). + message: + type: string + title: message + minLength: 1 + description: Message describing the maintenance (required). + from: + type: string + examples: + - "2024-03-01T02:00:00Z" + title: from + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: Start time of the maintenance window (RFC 3339 format, required). + to: + type: string + examples: + - "2024-03-01T06:00:00Z" title: to pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ - description: New end time (RFC 3339 format, optional). + description: End time of the maintenance window (RFC 3339 format, required). pageId: - type: - - string - - "null" + type: string title: page_id - description: 'Deprecated: page_id is now derived from page_component_ids.' - deprecated: true + minLength: 1 + description: Page ID to associate with this maintenance (required). pageComponentIds: type: array items: type: string title: page_component_ids - description: New list of page component IDs (optional, replaces existing list). - updatePageComponentIds: + description: Page component IDs to associate with this maintenance (optional). + notify: type: - boolean - "null" - title: update_page_component_ids - description: |- - Set to true to update page component associations. - When true, page_component_ids replaces the existing list (empty clears all). - When false or unset, page_component_ids is ignored and existing associations are preserved. - title: UpdateMaintenanceRequest + title: notify + description: Whether to notify subscribers about this maintenance (optional, defaults to false). + title: CreateMaintenanceRequest additionalProperties: false - description: UpdateMaintenanceRequest is the request to update a maintenance window. - openstatus.maintenance.v1.UpdateMaintenanceResponse: + description: CreateMaintenanceRequest is the request to create a new maintenance window. + openstatus.maintenance.v1.CreateMaintenanceResponse: type: object properties: maintenance: title: maintenance - description: The updated maintenance. + description: The created maintenance. $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' - title: UpdateMaintenanceResponse + title: CreateMaintenanceResponse additionalProperties: false - description: UpdateMaintenanceResponse is the response after updating a maintenance window. - openstatus.monitor.v1.BodyAssertion: + description: CreateMaintenanceResponse is the response after creating a maintenance window. + openstatus.maintenance.v1.DeleteMaintenanceRequest: type: object properties: - target: + id: type: string - title: target - description: Target value to compare against. - comparator: - not: - enum: - - STRING_COMPARATOR_UNSPECIFIED - title: comparator - description: Comparison operation (required, must not be UNSPECIFIED). - $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator' - title: BodyAssertion + title: id + minLength: 1 + description: ID of the maintenance to delete (required). + title: DeleteMaintenanceRequest additionalProperties: false - description: BodyAssertion defines an assertion for response body content. + description: DeleteMaintenanceRequest is the request to delete a maintenance window. + openstatus.maintenance.v1.DeleteMaintenanceResponse: + type: object + properties: + success: + type: boolean + title: success + description: Whether the deletion was successful. + title: DeleteMaintenanceResponse + additionalProperties: false + description: DeleteMaintenanceResponse is the response after deleting a maintenance window. + openstatus.maintenance.v1.GetMaintenanceRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the maintenance to retrieve (required). + title: GetMaintenanceRequest + additionalProperties: false + description: GetMaintenanceRequest is the request to get a maintenance window by ID. + openstatus.maintenance.v1.GetMaintenanceResponse: + type: object + properties: + maintenance: + title: maintenance + description: The requested maintenance. + $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' + title: GetMaintenanceResponse + additionalProperties: false + description: GetMaintenanceResponse is the response containing the maintenance window. + openstatus.maintenance.v1.ListMaintenancesRequest: + type: object + properties: + limit: + type: + - integer + - "null" + title: limit + maximum: 100 + minimum: 1 + format: int32 + description: Maximum number of maintenances to return (1-100, defaults to 50). + offset: + type: + - integer + - "null" + title: offset + minimum: 0 + format: int32 + description: Number of maintenances to skip for pagination (defaults to 0). + pageId: + type: + - string + - "null" + title: page_id + description: Filter by page ID (optional). + title: ListMaintenancesRequest + additionalProperties: false + description: ListMaintenancesRequest is the request to list maintenance windows. + openstatus.maintenance.v1.ListMaintenancesResponse: + type: object + properties: + maintenances: + type: array + items: + $ref: '#/components/schemas/openstatus.maintenance.v1.MaintenanceSummary' + title: maintenances + description: List of maintenances. + totalSize: + type: integer + title: total_size + format: int32 + description: Total number of maintenances matching the filter. + title: ListMaintenancesResponse + additionalProperties: false + description: ListMaintenancesResponse is the response containing maintenance window summaries. + openstatus.maintenance.v1.Maintenance: + type: object + properties: + id: + type: string + title: id + description: Unique identifier for the maintenance. + title: + type: string + title: title + description: Title of the maintenance. + message: + type: string + title: message + description: Message describing the maintenance. + from: + type: string + title: from + description: Start time of the maintenance window (RFC 3339 format). + to: + type: string + title: to + description: End time of the maintenance window (RFC 3339 format). + pageId: + type: string + title: page_id + description: ID of the page this maintenance is associated with. + pageComponentIds: + type: array + items: + type: string + title: page_component_ids + description: IDs of affected page components. + createdAt: + type: string + title: created_at + description: Timestamp when the maintenance was created (RFC 3339 format). + updatedAt: + type: string + title: updated_at + description: Timestamp when the maintenance was last updated (RFC 3339 format). + title: Maintenance + additionalProperties: false + description: Maintenance represents a maintenance window with full details. + openstatus.maintenance.v1.MaintenanceSummary: + type: object + properties: + id: + type: string + title: id + description: Unique identifier for the maintenance. + title: + type: string + title: title + description: Title of the maintenance. + message: + type: string + title: message + description: Message describing the maintenance. + from: + type: string + title: from + description: Start time of the maintenance window (RFC 3339 format). + to: + type: string + title: to + description: End time of the maintenance window (RFC 3339 format). + pageId: + type: string + title: page_id + description: ID of the page this maintenance is associated with. + pageComponentIds: + type: array + items: + type: string + title: page_component_ids + description: IDs of affected page components. + createdAt: + type: string + title: created_at + description: Timestamp when the maintenance was created (RFC 3339 format). + updatedAt: + type: string + title: updated_at + description: Timestamp when the maintenance was last updated (RFC 3339 format). + title: MaintenanceSummary + additionalProperties: false + description: MaintenanceSummary represents metadata for a maintenance window (used in list responses). + openstatus.maintenance.v1.UpdateMaintenanceRequest: + type: object + properties: + id: + type: string + title: id + minLength: 1 + description: ID of the maintenance to update (required). + title: + type: + - string + - "null" + title: title + maxLength: 256 + minLength: 1 + description: New title for the maintenance (optional). + message: + type: + - string + - "null" + title: message + description: New message for the maintenance (optional). + from: + type: + - string + - "null" + title: from + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: New start time (RFC 3339 format, optional). + to: + type: + - string + - "null" + title: to + pattern: ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})$ + description: New end time (RFC 3339 format, optional). + pageId: + type: + - string + - "null" + title: page_id + description: 'Deprecated: page_id is now derived from page_component_ids.' + deprecated: true + pageComponentIds: + type: array + items: + type: string + title: page_component_ids + description: New list of page component IDs (optional, replaces existing list). + updatePageComponentIds: + type: + - boolean + - "null" + title: update_page_component_ids + description: |- + Set to true to update page component associations. + When true, page_component_ids replaces the existing list (empty clears all). + When false or unset, page_component_ids is ignored and existing associations are preserved. + title: UpdateMaintenanceRequest + additionalProperties: false + description: UpdateMaintenanceRequest is the request to update a maintenance window. + openstatus.maintenance.v1.UpdateMaintenanceResponse: + type: object + properties: + maintenance: + title: maintenance + description: The updated maintenance. + $ref: '#/components/schemas/openstatus.maintenance.v1.Maintenance' + title: UpdateMaintenanceResponse + additionalProperties: false + description: UpdateMaintenanceResponse is the response after updating a maintenance window. + openstatus.monitor.v1.BodyAssertion: + type: object + properties: + target: + type: string + title: target + description: Target value to compare against. + comparator: + not: + enum: + - STRING_COMPARATOR_UNSPECIFIED + title: comparator + description: Comparison operation (required, must not be UNSPECIFIED). + $ref: '#/components/schemas/openstatus.monitor.v1.StringComparator' + title: BodyAssertion + additionalProperties: false + description: BodyAssertion defines an assertion for response body content. openstatus.monitor.v1.CreateDNSMonitorRequest: type: object properties: @@ -4852,8 +5641,17 @@ components: Per-component impacts set by the initial update (optional). When provided, the named components are added to the report's affected set. Omitting this field creates a legacy report without impact tracking. - title: CreateStatusReportRequest - additionalProperties: false + incidentId: + type: + - string + - "null" + title: incident_id + minLength: 1 + description: |- + ID of an incident to link the report to (optional). Linked in the same + step: a closed incident, or one already linked to a report, fails the create. + title: CreateStatusReportRequest + additionalProperties: false description: CreateStatusReportRequest is the request to create a new status report. openstatus.status_report.v1.CreateStatusReportResponse: type: object @@ -5000,6 +5798,12 @@ components: type: string title: updated_at description: Timestamp when the report was last updated (RFC 3339 format). + incidentId: + type: + - string + - "null" + title: incident_id + description: ID of the incident this report communicates (unset when not linked). title: StatusReport additionalProperties: false description: StatusReport represents an incident or maintenance report with full details. @@ -5042,6 +5846,12 @@ components: type: string title: updated_at description: Timestamp when the report was last updated (RFC 3339 format). + incidentId: + type: + - string + - "null" + title: incident_id + description: ID of the incident this report communicates (unset when not linked). title: StatusReportSummary additionalProperties: false description: StatusReportSummary represents metadata for a status report (used in list responses). @@ -5136,8 +5946,12 @@ tags: and associate them with monitors. Supports 12 notification providers. - name: StatusReportService description: | - Create and manage incident reports with status updates. Reports follow a lifecycle: + Create and manage public status reports with status updates. Reports follow a lifecycle: investigating -> identified -> monitoring -> resolved. + - name: IncidentService + description: | + Declare and run incidents: lifecycle (open -> mitigated -> resolved, or canceled), + timeline notes, a linked status report, and the postmortem. - name: MaintenanceService description: | Schedule maintenance windows for status page components. Subscribers can be @@ -5205,6 +6019,454 @@ paths: application/json: schema: $ref: '#/components/schemas/openstatus.health.v1.CheckResponse' + /rpc/openstatus.incident.v1.IncidentService/AddIncidentNote: + post: + tags: + - IncidentService + summary: AddIncidentNote + description: AddIncidentNote appends a note to the incident timeline. + operationId: IncidentService_AddIncidentNote + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.AddIncidentNoteRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.AddIncidentNoteResponse' + /rpc/openstatus.incident.v1.IncidentService/ApprovePostmortem: + post: + tags: + - IncidentService + summary: ApprovePostmortem + description: Approves the draft postmortem of a resolved incident. With close set to true the incident is closed in the same step. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this. + operationId: IncidentService_ApprovePostmortem + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ApprovePostmortemRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ApprovePostmortemResponse' + /rpc/openstatus.incident.v1.IncidentService/CloseIncident: + post: + tags: + - IncidentService + summary: CloseIncident + description: Closes a resolved incident, after which only its postmortem can change. Requires an approved postmortem, or skip_postmortem set to true. Allowed for workspace owners and admins and for the incident commander. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this. + operationId: IncidentService_CloseIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.CloseIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.CloseIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/DeclareIncident: + post: + tags: + - IncidentService + summary: DeclareIncident + description: Declares a new incident in the open status. The commander is set by email and must be a member of the workspace; without one the incident is unassigned. A newly assigned commander is emailed unless they own the API key. When open_slack_channel is true and Slack is connected, a dedicated channel is created and the team invited. started_at defaults to now and can be set in the past for a retroactive declare. + operationId: IncidentService_DeclareIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeclareIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeclareIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/DeleteIncident: + post: + tags: + - IncidentService + summary: DeleteIncident + description: Deletes an incident declared by mistake. Only allowed while the incident is open and was never mitigated, resolved or closed (Incident.deletable); cancel it otherwise. Allowed for workspace owners and admins only. The API key acts as its creator, with the creator's current role; keys without a creator cannot call this. + operationId: IncidentService_DeleteIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeleteIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.DeleteIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/GetIncident: + get: + tags: + - IncidentService + summary: GetIncident + description: GetIncident retrieves an incident by ID, including its timeline. + operationId: IncidentService_GetIncident.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentRequest' + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentResponse' + post: + tags: + - IncidentService + summary: GetIncident + description: GetIncident retrieves an incident by ID, including its timeline. + operationId: IncidentService_GetIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/GetPostmortem: + get: + tags: + - IncidentService + summary: GetPostmortem + description: GetPostmortem retrieves the postmortem of an incident, if any. + operationId: IncidentService_GetPostmortem.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemRequest' + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemResponse' + post: + tags: + - IncidentService + summary: GetPostmortem + description: GetPostmortem retrieves the postmortem of an incident, if any. + operationId: IncidentService_GetPostmortem + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.GetPostmortemResponse' + /rpc/openstatus.incident.v1.IncidentService/LinkStatusReport: + post: + tags: + - IncidentService + summary: LinkStatusReport + description: LinkStatusReport links a public status report to the incident. + operationId: IncidentService_LinkStatusReport + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.LinkStatusReportRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.LinkStatusReportResponse' + /rpc/openstatus.incident.v1.IncidentService/ListIncidents: + get: + tags: + - IncidentService + summary: ListIncidents + description: ListIncidents returns the incidents of the workspace (metadata only), open first. + operationId: IncidentService_ListIncidents.get + parameters: + - name: message + in: query + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsRequest' + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsResponse' + post: + tags: + - IncidentService + summary: ListIncidents + description: ListIncidents returns the incidents of the workspace (metadata only), open first. + operationId: IncidentService_ListIncidents + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.ListIncidentsResponse' + /rpc/openstatus.incident.v1.IncidentService/SetIncidentStatus: + post: + tags: + - IncidentService + summary: SetIncidentStatus + description: 'Moves an incident to a new status. Allowed transitions: open -> mitigated, resolved or canceled; mitigated -> resolved, open or canceled; resolved -> open. Canceled is terminal and closes the incident. The same status, a forbidden transition or a closed incident fail with failed_precondition; Incident.allowed_transitions lists what is valid. A linked status report is never updated: post the public update with StatusReportService.AddStatusReportUpdate.' + operationId: IncidentService_SetIncidentStatus + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.SetIncidentStatusRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.SetIncidentStatusResponse' + /rpc/openstatus.incident.v1.IncidentService/UnlinkStatusReport: + post: + tags: + - IncidentService + summary: UnlinkStatusReport + description: UnlinkStatusReport removes the link to the incident's status report. + operationId: IncidentService_UnlinkStatusReport + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UnlinkStatusReportRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UnlinkStatusReportResponse' + /rpc/openstatus.incident.v1.IncidentService/UpdateIncident: + post: + tags: + - IncidentService + summary: UpdateIncident + description: UpdateIncident edits the title, severity, summary, commander or start time of an incident. + operationId: IncidentService_UpdateIncident + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdateIncidentRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdateIncidentResponse' + /rpc/openstatus.incident.v1.IncidentService/UpdatePostmortem: + post: + tags: + - IncidentService + summary: UpdatePostmortem + description: UpdatePostmortem creates or replaces the postmortem body of a resolved incident. + operationId: IncidentService_UpdatePostmortem + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdatePostmortemRequest' + required: true + responses: + "429": + $ref: '#/components/responses/RateLimited' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/connect.error' + "200": + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/openstatus.incident.v1.UpdatePostmortemResponse' /rpc/openstatus.maintenance.v1.MaintenanceService/CreateMaintenance: post: tags: diff --git a/packages/proto/gen/ts/openstatus/incident/v1/incident_pb.ts b/packages/proto/gen/ts/openstatus/incident/v1/incident_pb.ts new file mode 100644 index 00000000..87d66479 --- /dev/null +++ b/packages/proto/gen/ts/openstatus/incident/v1/incident_pb.ts @@ -0,0 +1,684 @@ +// @generated by protoc-gen-es v2.15.0 with parameter "target=ts,import_extension=.ts" +// @generated from file openstatus/incident/v1/incident.proto (package openstatus.incident.v1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2"; +import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2"; +import type { StatusReportStatus } from "../../status_report/v1/status_report_pb.ts"; +import { file_openstatus_status_report_v1_status_report } from "../../status_report/v1/status_report_pb.ts"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file openstatus/incident/v1/incident.proto. + */ +export const file_openstatus_incident_v1_incident: GenFile = /*@__PURE__*/ + fileDesc("CiVvcGVuc3RhdHVzL2luY2lkZW50L3YxL2luY2lkZW50LnByb3RvEhZvcGVuc3RhdHVzLmluY2lkZW50LnYxIisKDEluY2lkZW50VXNlchINCgVlbWFpbBgBIAEoCRIMCgRuYW1lGAIgASgJIoMBChRJbmNpZGVudFN0YXR1c1JlcG9ydBIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRI/CgZzdGF0dXMYAyABKA4yLy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0U3RhdHVzEg8KB3BhZ2VfaWQYBCABKAkixwEKDUluY2lkZW50RXZlbnQSCgoCaWQYASABKAkSNwoEdHlwZRgCIAEoDjIpLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnRFdmVudFR5cGUSDwoHbWVzc2FnZRgDIAEoCRI9CgpjcmVhdGVkX2J5GAQgASgLMiQub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudFVzZXJIAIgBARISCgpjcmVhdGVkX2F0GAUgASgJQg0KC19jcmVhdGVkX2J5IukDCg9JbmNpZGVudFN1bW1hcnkSCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSOgoIc2V2ZXJpdHkYAyABKA4yKC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkluY2lkZW50U2V2ZXJpdHkSNgoGc3RhdHVzGAQgASgOMiYub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudFN0YXR1cxI8Cgljb21tYW5kZXIYBSABKAsyJC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkluY2lkZW50VXNlckgAiAEBEhMKC2RlY2xhcmVkX2F0GAYgASgJEhIKCnN0YXJ0ZWRfYXQYByABKAkSGAoLcmVzb2x2ZWRfYXQYCCABKAlIAYgBARIWCgljbG9zZWRfYXQYCSABKAlIAogBARJICg1zdGF0dXNfcmVwb3J0GAogASgLMiwub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudFN0YXR1c1JlcG9ydEgDiAEBEhIKCmNyZWF0ZWRfYXQYCyABKAkSEgoKdXBkYXRlZF9hdBgMIAEoCUIMCgpfY29tbWFuZGVyQg4KDF9yZXNvbHZlZF9hdEIMCgpfY2xvc2VkX2F0QhAKDl9zdGF0dXNfcmVwb3J0IpUHCghJbmNpZGVudBIKCgJpZBgBIAEoCRINCgV0aXRsZRgCIAEoCRI6CghzZXZlcml0eRgDIAEoDjIoLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnRTZXZlcml0eRI2CgZzdGF0dXMYBCABKA4yJi5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkluY2lkZW50U3RhdHVzEhQKB3N1bW1hcnkYBSABKAlIAIgBARI8Cgljb21tYW5kZXIYBiABKAsyJC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkluY2lkZW50VXNlckgBiAEBEj4KC2RlY2xhcmVkX2J5GAcgASgLMiQub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudFVzZXJIAogBARI+CgtyZXNvbHZlZF9ieRgIIAEoCzIkLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnRVc2VySAOIAQESEwoLZGVjbGFyZWRfYXQYCSABKAkSEgoKc3RhcnRlZF9hdBgKIAEoCRIZCgxtaXRpZ2F0ZWRfYXQYCyABKAlIBIgBARIYCgtyZXNvbHZlZF9hdBgMIAEoCUgFiAEBEhYKCWNsb3NlZF9hdBgNIAEoCUgGiAEBEkgKDXN0YXR1c19yZXBvcnQYDiABKAsyLC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkluY2lkZW50U3RhdHVzUmVwb3J0SAeIAQESHgoRc2xhY2tfY2hhbm5lbF91cmwYDyABKAlICIgBARJDChNhbGxvd2VkX3RyYW5zaXRpb25zGBAgAygOMiYub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudFN0YXR1cxIRCglkZWxldGFibGUYESABKAgSNQoGZXZlbnRzGBIgAygLMiUub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudEV2ZW50EhIKCmNyZWF0ZWRfYXQYEyABKAkSEgoKdXBkYXRlZF9hdBgUIAEoCUIKCghfc3VtbWFyeUIMCgpfY29tbWFuZGVyQg4KDF9kZWNsYXJlZF9ieUIOCgxfcmVzb2x2ZWRfYnlCDwoNX21pdGlnYXRlZF9hdEIOCgxfcmVzb2x2ZWRfYXRCDAoKX2Nsb3NlZF9hdEIQCg5fc3RhdHVzX3JlcG9ydEIUChJfc2xhY2tfY2hhbm5lbF91cmwizAIKClBvc3Rtb3J0ZW0SEwoLaW5jaWRlbnRfaWQYASABKAkSOAoGc3RhdHVzGAIgASgOMigub3BlbnN0YXR1cy5pbmNpZGVudC52MS5Qb3N0bW9ydGVtU3RhdHVzEg8KB2NvbnRlbnQYAyABKAkSPAoKZHJhZnRlZF9ieRgEIAEoDjIoLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuUG9zdG1vcnRlbUF1dGhvchI+CgthcHByb3ZlZF9ieRgFIAEoCzIkLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnRVc2VySACIAQESGAoLYXBwcm92ZWRfYXQYBiABKAlIAYgBARISCgpjcmVhdGVkX2F0GAcgASgJEhIKCnVwZGF0ZWRfYXQYCCABKAlCDgoMX2FwcHJvdmVkX2J5Qg4KDF9hcHByb3ZlZF9hdCqPAQoQSW5jaWRlbnRTZXZlcml0eRIhCh1JTkNJREVOVF9TRVZFUklUWV9VTlNQRUNJRklFRBAAEh4KGklOQ0lERU5UX1NFVkVSSVRZX0NSSVRJQ0FMEAESGwoXSU5DSURFTlRfU0VWRVJJVFlfTUFKT1IQAhIbChdJTkNJREVOVF9TRVZFUklUWV9NSU5PUhADKqYBCg5JbmNpZGVudFN0YXR1cxIfChtJTkNJREVOVF9TVEFUVVNfVU5TUEVDSUZJRUQQABIYChRJTkNJREVOVF9TVEFUVVNfT1BFThABEh0KGUlOQ0lERU5UX1NUQVRVU19NSVRJR0FURUQQAhIcChhJTkNJREVOVF9TVEFUVVNfUkVTT0xWRUQQAxIcChhJTkNJREVOVF9TVEFUVVNfQ0FOQ0VMRUQQBCrEBQoRSW5jaWRlbnRFdmVudFR5cGUSIwofSU5DSURFTlRfRVZFTlRfVFlQRV9VTlNQRUNJRklFRBAAEiAKHElOQ0lERU5UX0VWRU5UX1RZUEVfREVDTEFSRUQQARIoCiRJTkNJREVOVF9FVkVOVF9UWVBFX1NFVkVSSVRZX0NIQU5HRUQQAhImCiJJTkNJREVOVF9FVkVOVF9UWVBFX1NUQVRVU19DSEFOR0VEEAMSKQolSU5DSURFTlRfRVZFTlRfVFlQRV9DT01NQU5ERVJfQ0hBTkdFRBAEEioKJklOQ0lERU5UX0VWRU5UX1RZUEVfU1RBUlRFRF9BVF9DSEFOR0VEEAUSHAoYSU5DSURFTlRfRVZFTlRfVFlQRV9OT1RFEAYSLAooSU5DSURFTlRfRVZFTlRfVFlQRV9TVEFUVVNfUkVQT1JUX0xJTktFRBAHEi4KKklOQ0lERU5UX0VWRU5UX1RZUEVfU1RBVFVTX1JFUE9SVF9VTkxJTktFRBAIEisKJ0lOQ0lERU5UX0VWRU5UX1RZUEVfU0xBQ0tfQ0hBTk5FTF9CT1VORBAJEi0KKUlOQ0lERU5UX0VWRU5UX1RZUEVfU0xBQ0tfQ0hBTk5FTF9VTkJPVU5EEAoSIAocSU5DSURFTlRfRVZFTlRfVFlQRV9SRVNPTFZFRBALEiAKHElOQ0lERU5UX0VWRU5UX1RZUEVfQ0FOQ0VMRUQQDBIqCiZJTkNJREVOVF9FVkVOVF9UWVBFX1BPU1RNT1JURU1fRFJBRlRFRBANEioKJklOQ0lERU5UX0VWRU5UX1RZUEVfUE9TVE1PUlRFTV9VUERBVEVEEA4SKwonSU5DSURFTlRfRVZFTlRfVFlQRV9QT1NUTU9SVEVNX0FQUFJPVkVEEA8SHgoaSU5DSURFTlRfRVZFTlRfVFlQRV9DTE9TRUQQECpyChBQb3N0bW9ydGVtU3RhdHVzEiEKHVBPU1RNT1JURU1fU1RBVFVTX1VOU1BFQ0lGSUVEEAASGwoXUE9TVE1PUlRFTV9TVEFUVVNfRFJBRlQQARIeChpQT1NUTU9SVEVNX1NUQVRVU19BUFBST1ZFRBACKm4KEFBvc3Rtb3J0ZW1BdXRob3ISIQodUE9TVE1PUlRFTV9BVVRIT1JfVU5TUEVDSUZJRUQQABIbChdQT1NUTU9SVEVNX0FVVEhPUl9BR0VOVBABEhoKFlBPU1RNT1JURU1fQVVUSE9SX1VTRVIQAkJVWlNnaXRodWIuY29tL29wZW5zdGF0dXNocS9vcGVuc3RhdHVzL3BhY2thZ2VzL3Byb3RvL29wZW5zdGF0dXMvaW5jaWRlbnQvdjE7aW5jaWRlbnR2MWIGcHJvdG8z", [file_openstatus_status_report_v1_status_report]); + +/** + * IncidentUser is a workspace member referenced by an incident. + * + * @generated from message openstatus.incident.v1.IncidentUser + */ +export type IncidentUser = Message<"openstatus.incident.v1.IncidentUser"> & { + /** + * Email address of the member (empty for a deleted account). + * + * @generated from field: string email = 1; + */ + email: string; + + /** + * Display name of the member ("Deleted user" for a deleted account). + * + * @generated from field: string name = 2; + */ + name: string; +}; + +/** + * Describes the message openstatus.incident.v1.IncidentUser. + * Use `create(IncidentUserSchema)` to create a new message. + */ +export const IncidentUserSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_incident, 0); + +/** + * IncidentStatusReport is the public status report linked to an incident. + * + * @generated from message openstatus.incident.v1.IncidentStatusReport + */ +export type IncidentStatusReport = Message<"openstatus.incident.v1.IncidentStatusReport"> & { + /** + * ID of the status report. + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * Title of the status report. + * + * @generated from field: string title = 2; + */ + title: string; + + /** + * Current status of the status report. + * + * @generated from field: openstatus.status_report.v1.StatusReportStatus status = 3; + */ + status: StatusReportStatus; + + /** + * ID of the status page the report belongs to. + * + * @generated from field: string page_id = 4; + */ + pageId: string; +}; + +/** + * Describes the message openstatus.incident.v1.IncidentStatusReport. + * Use `create(IncidentStatusReportSchema)` to create a new message. + */ +export const IncidentStatusReportSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_incident, 1); + +/** + * IncidentEvent is one entry of an incident timeline. + * + * @generated from message openstatus.incident.v1.IncidentEvent + */ +export type IncidentEvent = Message<"openstatus.incident.v1.IncidentEvent"> & { + /** + * Unique identifier for the event. + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * Kind of event. + * + * @generated from field: openstatus.incident.v1.IncidentEventType type = 2; + */ + type: IncidentEventType; + + /** + * Text of the event: the note for notes, a rendered summary otherwise. + * + * @generated from field: string message = 3; + */ + message: string; + + /** + * Member who caused the event (unset for system events and API keys without a creator). + * + * @generated from field: optional openstatus.incident.v1.IncidentUser created_by = 4; + */ + createdBy?: IncidentUser | undefined; + + /** + * Timestamp when the event was recorded (RFC 3339 format). + * + * @generated from field: string created_at = 5; + */ + createdAt: string; +}; + +/** + * Describes the message openstatus.incident.v1.IncidentEvent. + * Use `create(IncidentEventSchema)` to create a new message. + */ +export const IncidentEventSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_incident, 2); + +/** + * IncidentSummary is the metadata of an incident (used in list responses). + * + * @generated from message openstatus.incident.v1.IncidentSummary + */ +export type IncidentSummary = Message<"openstatus.incident.v1.IncidentSummary"> & { + /** + * Unique identifier for the incident. + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * Title of the incident. + * + * @generated from field: string title = 2; + */ + title: string; + + /** + * Severity of the incident. + * + * @generated from field: openstatus.incident.v1.IncidentSeverity severity = 3; + */ + severity: IncidentSeverity; + + /** + * Current status of the incident. + * + * @generated from field: openstatus.incident.v1.IncidentStatus status = 4; + */ + status: IncidentStatus; + + /** + * Member leading the response (unset when unassigned). + * + * @generated from field: optional openstatus.incident.v1.IncidentUser commander = 5; + */ + commander?: IncidentUser | undefined; + + /** + * Timestamp when the incident was declared (RFC 3339 format). + * + * @generated from field: string declared_at = 6; + */ + declaredAt: string; + + /** + * Timestamp when the impact began (RFC 3339 format). + * + * @generated from field: string started_at = 7; + */ + startedAt: string; + + /** + * Timestamp of the last resolution (RFC 3339 format). + * + * @generated from field: optional string resolved_at = 8; + */ + resolvedAt?: string | undefined; + + /** + * Timestamp when the incident was closed or canceled (RFC 3339 format). + * + * @generated from field: optional string closed_at = 9; + */ + closedAt?: string | undefined; + + /** + * Linked public status report. + * + * @generated from field: optional openstatus.incident.v1.IncidentStatusReport status_report = 10; + */ + statusReport?: IncidentStatusReport | undefined; + + /** + * Timestamp when the incident was created (RFC 3339 format). + * + * @generated from field: string created_at = 11; + */ + createdAt: string; + + /** + * Timestamp when the incident was last updated (RFC 3339 format). + * + * @generated from field: string updated_at = 12; + */ + updatedAt: string; +}; + +/** + * Describes the message openstatus.incident.v1.IncidentSummary. + * Use `create(IncidentSummarySchema)` to create a new message. + */ +export const IncidentSummarySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_incident, 3); + +/** + * Incident is a managed incident with full details. + * + * @generated from message openstatus.incident.v1.Incident + */ +export type Incident = Message<"openstatus.incident.v1.Incident"> & { + /** + * Unique identifier for the incident. + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * Title of the incident. + * + * @generated from field: string title = 2; + */ + title: string; + + /** + * Severity of the incident. + * + * @generated from field: openstatus.incident.v1.IncidentSeverity severity = 3; + */ + severity: IncidentSeverity; + + /** + * Current status of the incident. + * + * @generated from field: openstatus.incident.v1.IncidentStatus status = 4; + */ + status: IncidentStatus; + + /** + * Short human summary. + * + * @generated from field: optional string summary = 5; + */ + summary?: string | undefined; + + /** + * Member leading the response (unset when unassigned). + * + * @generated from field: optional openstatus.incident.v1.IncidentUser commander = 6; + */ + commander?: IncidentUser | undefined; + + /** + * Member who declared the incident (unset for API keys without a creator). + * + * @generated from field: optional openstatus.incident.v1.IncidentUser declared_by = 7; + */ + declaredBy?: IncidentUser | undefined; + + /** + * Member who last resolved the incident. + * + * @generated from field: optional openstatus.incident.v1.IncidentUser resolved_by = 8; + */ + resolvedBy?: IncidentUser | undefined; + + /** + * Timestamp when the incident was declared (RFC 3339 format). + * + * @generated from field: string declared_at = 9; + */ + declaredAt: string; + + /** + * Timestamp when the impact began (RFC 3339 format). + * + * @generated from field: string started_at = 10; + */ + startedAt: string; + + /** + * Timestamp when the incident was first mitigated (RFC 3339 format). + * + * @generated from field: optional string mitigated_at = 11; + */ + mitigatedAt?: string | undefined; + + /** + * Timestamp of the last resolution (RFC 3339 format). + * + * @generated from field: optional string resolved_at = 12; + */ + resolvedAt?: string | undefined; + + /** + * Timestamp when the incident was closed or canceled (RFC 3339 format). + * A closed incident is read-only except for its postmortem. + * + * @generated from field: optional string closed_at = 13; + */ + closedAt?: string | undefined; + + /** + * Linked public status report. + * + * @generated from field: optional openstatus.incident.v1.IncidentStatusReport status_report = 14; + */ + statusReport?: IncidentStatusReport | undefined; + + /** + * Link to the bound Slack channel. + * + * @generated from field: optional string slack_channel_url = 15; + */ + slackChannelUrl?: string | undefined; + + /** + * Statuses SetIncidentStatus accepts from the current state (empty once closed). + * + * @generated from field: repeated openstatus.incident.v1.IncidentStatus allowed_transitions = 16; + */ + allowedTransitions: IncidentStatus[]; + + /** + * Whether DeleteIncident is allowed: only while open and never mitigated, resolved or closed. + * + * @generated from field: bool deletable = 17; + */ + deletable: boolean; + + /** + * Timeline, newest first (only included in GetIncident). + * + * @generated from field: repeated openstatus.incident.v1.IncidentEvent events = 18; + */ + events: IncidentEvent[]; + + /** + * Timestamp when the incident was created (RFC 3339 format). + * + * @generated from field: string created_at = 19; + */ + createdAt: string; + + /** + * Timestamp when the incident was last updated (RFC 3339 format). + * + * @generated from field: string updated_at = 20; + */ + updatedAt: string; +}; + +/** + * Describes the message openstatus.incident.v1.Incident. + * Use `create(IncidentSchema)` to create a new message. + */ +export const IncidentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_incident, 4); + +/** + * Postmortem is the review written after an incident is resolved. + * + * @generated from message openstatus.incident.v1.Postmortem + */ +export type Postmortem = Message<"openstatus.incident.v1.Postmortem"> & { + /** + * ID of the incident the postmortem belongs to. + * + * @generated from field: string incident_id = 1; + */ + incidentId: string; + + /** + * Review state of the postmortem. + * + * @generated from field: openstatus.incident.v1.PostmortemStatus status = 2; + */ + status: PostmortemStatus; + + /** + * Markdown body of the postmortem. + * + * @generated from field: string content = 3; + */ + content: string; + + /** + * Who wrote the current body. + * + * @generated from field: openstatus.incident.v1.PostmortemAuthor drafted_by = 4; + */ + draftedBy: PostmortemAuthor; + + /** + * Member who approved the postmortem. + * + * @generated from field: optional openstatus.incident.v1.IncidentUser approved_by = 5; + */ + approvedBy?: IncidentUser | undefined; + + /** + * Timestamp when the postmortem was approved (RFC 3339 format). + * + * @generated from field: optional string approved_at = 6; + */ + approvedAt?: string | undefined; + + /** + * Timestamp when the postmortem was created (RFC 3339 format). + * + * @generated from field: string created_at = 7; + */ + createdAt: string; + + /** + * Timestamp when the postmortem was last updated (RFC 3339 format). + * + * @generated from field: string updated_at = 8; + */ + updatedAt: string; +}; + +/** + * Describes the message openstatus.incident.v1.Postmortem. + * Use `create(PostmortemSchema)` to create a new message. + */ +export const PostmortemSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_incident, 5); + +/** + * IncidentSeverity is how bad an incident is. + * + * @generated from enum openstatus.incident.v1.IncidentSeverity + */ +export enum IncidentSeverity { + /** + * @generated from enum value: INCIDENT_SEVERITY_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: INCIDENT_SEVERITY_CRITICAL = 1; + */ + CRITICAL = 1, + + /** + * @generated from enum value: INCIDENT_SEVERITY_MAJOR = 2; + */ + MAJOR = 2, + + /** + * @generated from enum value: INCIDENT_SEVERITY_MINOR = 3; + */ + MINOR = 3, +} + +/** + * Describes the enum openstatus.incident.v1.IncidentSeverity. + */ +export const IncidentSeveritySchema: GenEnum = /*@__PURE__*/ + enumDesc(file_openstatus_incident_v1_incident, 0); + +/** + * IncidentStatus is the lifecycle state of an incident. + * Closed is not a status: a closed incident has closed_at set and keeps its last status. + * + * @generated from enum openstatus.incident.v1.IncidentStatus + */ +export enum IncidentStatus { + /** + * @generated from enum value: INCIDENT_STATUS_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: INCIDENT_STATUS_OPEN = 1; + */ + OPEN = 1, + + /** + * @generated from enum value: INCIDENT_STATUS_MITIGATED = 2; + */ + MITIGATED = 2, + + /** + * @generated from enum value: INCIDENT_STATUS_RESOLVED = 3; + */ + RESOLVED = 3, + + /** + * @generated from enum value: INCIDENT_STATUS_CANCELED = 4; + */ + CANCELED = 4, +} + +/** + * Describes the enum openstatus.incident.v1.IncidentStatus. + */ +export const IncidentStatusSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_openstatus_incident_v1_incident, 1); + +/** + * IncidentEventType is the kind of entry in an incident timeline. + * + * @generated from enum openstatus.incident.v1.IncidentEventType + */ +export enum IncidentEventType { + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_DECLARED = 1; + */ + DECLARED = 1, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_SEVERITY_CHANGED = 2; + */ + SEVERITY_CHANGED = 2, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_STATUS_CHANGED = 3; + */ + STATUS_CHANGED = 3, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_COMMANDER_CHANGED = 4; + */ + COMMANDER_CHANGED = 4, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_STARTED_AT_CHANGED = 5; + */ + STARTED_AT_CHANGED = 5, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_NOTE = 6; + */ + NOTE = 6, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_STATUS_REPORT_LINKED = 7; + */ + STATUS_REPORT_LINKED = 7, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_STATUS_REPORT_UNLINKED = 8; + */ + STATUS_REPORT_UNLINKED = 8, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_SLACK_CHANNEL_BOUND = 9; + */ + SLACK_CHANNEL_BOUND = 9, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_SLACK_CHANNEL_UNBOUND = 10; + */ + SLACK_CHANNEL_UNBOUND = 10, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_RESOLVED = 11; + */ + RESOLVED = 11, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_CANCELED = 12; + */ + CANCELED = 12, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_POSTMORTEM_DRAFTED = 13; + */ + POSTMORTEM_DRAFTED = 13, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_POSTMORTEM_UPDATED = 14; + */ + POSTMORTEM_UPDATED = 14, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_POSTMORTEM_APPROVED = 15; + */ + POSTMORTEM_APPROVED = 15, + + /** + * @generated from enum value: INCIDENT_EVENT_TYPE_CLOSED = 16; + */ + CLOSED = 16, +} + +/** + * Describes the enum openstatus.incident.v1.IncidentEventType. + */ +export const IncidentEventTypeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_openstatus_incident_v1_incident, 2); + +/** + * PostmortemStatus is the review state of a postmortem. + * + * @generated from enum openstatus.incident.v1.PostmortemStatus + */ +export enum PostmortemStatus { + /** + * @generated from enum value: POSTMORTEM_STATUS_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: POSTMORTEM_STATUS_DRAFT = 1; + */ + DRAFT = 1, + + /** + * @generated from enum value: POSTMORTEM_STATUS_APPROVED = 2; + */ + APPROVED = 2, +} + +/** + * Describes the enum openstatus.incident.v1.PostmortemStatus. + */ +export const PostmortemStatusSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_openstatus_incident_v1_incident, 3); + +/** + * PostmortemAuthor is who wrote the current postmortem body. + * + * @generated from enum openstatus.incident.v1.PostmortemAuthor + */ +export enum PostmortemAuthor { + /** + * @generated from enum value: POSTMORTEM_AUTHOR_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: POSTMORTEM_AUTHOR_AGENT = 1; + */ + AGENT = 1, + + /** + * @generated from enum value: POSTMORTEM_AUTHOR_USER = 2; + */ + USER = 2, +} + +/** + * Describes the enum openstatus.incident.v1.PostmortemAuthor. + */ +export const PostmortemAuthorSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_openstatus_incident_v1_incident, 4); + diff --git a/packages/proto/gen/ts/openstatus/incident/v1/index.ts b/packages/proto/gen/ts/openstatus/incident/v1/index.ts new file mode 100644 index 00000000..05fb6e26 --- /dev/null +++ b/packages/proto/gen/ts/openstatus/incident/v1/index.ts @@ -0,0 +1,3 @@ +// Incident service exports +export * from "./incident_pb.js"; +export * from "./service_pb.js"; diff --git a/packages/proto/gen/ts/openstatus/incident/v1/service_pb.ts b/packages/proto/gen/ts/openstatus/incident/v1/service_pb.ts new file mode 100644 index 00000000..cdf0e95c --- /dev/null +++ b/packages/proto/gen/ts/openstatus/incident/v1/service_pb.ts @@ -0,0 +1,878 @@ +// @generated by protoc-gen-es v2.15.0 with parameter "target=ts,import_extension=.ts" +// @generated from file openstatus/incident/v1/service.proto (package openstatus.incident.v1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import { file_buf_validate_validate } from "../../../buf/validate/validate_pb.ts"; +import { file_gnostic_openapi_v3_annotations } from "../../../gnostic/openapi/v3/annotations_pb.ts"; +import type { Incident, IncidentEvent, IncidentSeverity, IncidentStatus, IncidentSummary, Postmortem } from "./incident_pb.ts"; +import { file_openstatus_incident_v1_incident } from "./incident_pb.ts"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file openstatus/incident/v1/service.proto. + */ +export const file_openstatus_incident_v1_service: GenFile = /*@__PURE__*/ + fileDesc("CiRvcGVuc3RhdHVzL2luY2lkZW50L3YxL3NlcnZpY2UucHJvdG8SFm9wZW5zdGF0dXMuaW5jaWRlbnQudjEioAQKFkRlY2xhcmVJbmNpZGVudFJlcXVlc3QSOAoFdGl0bGUYASABKAlCKbpHHDoaEhhDaGVja291dCBBUEkgcmV0dXJucyA1MDK6SAdyBRABGIACEkYKCHNldmVyaXR5GAIgASgOMigub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudFNldmVyaXR5Qgq6SAeCAQQQASAAEh4KB3N1bW1hcnkYAyABKAlCCLpIBXIDGKAfSACIAQESPAoPY29tbWFuZGVyX2VtYWlsGAQgASgJQh66RxQ6EhIQamFuZUBleGFtcGxlLmNvbbpIBHICYAFIAYgBARKBAQoKc3RhcnRlZF9hdBgFIAEoCUJoukcaOhgSFiIyMDI0LTAzLTE1VDEwOjMwOjAwWiK6SEhyRjJEXlxkezR9LVxkezJ9LVxkezJ9VFxkezJ9OlxkezJ9OlxkezJ9KFwuXGR7MSw5fSk/KFp8WystXVxkezJ9OlxkezJ9KSRIAogBARImChBzdGF0dXNfcmVwb3J0X2lkGAYgASgJQge6SARyAhABSAOIAQESHwoSb3Blbl9zbGFja19jaGFubmVsGAcgASgISASIAQFCCgoIX3N1bW1hcnlCEgoQX2NvbW1hbmRlcl9lbWFpbEINCgtfc3RhcnRlZF9hdEITChFfc3RhdHVzX3JlcG9ydF9pZEIVChNfb3Blbl9zbGFja19jaGFubmVsIk0KF0RlY2xhcmVJbmNpZGVudFJlc3BvbnNlEjIKCGluY2lkZW50GAEgASgLMiAub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudCIpChJHZXRJbmNpZGVudFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAEiSQoTR2V0SW5jaWRlbnRSZXNwb25zZRIyCghpbmNpZGVudBgBIAEoCzIgLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnQi0wEKFExpc3RJbmNpZGVudHNSZXF1ZXN0Eh0KBWxpbWl0GAEgASgFQgm6SAYaBBhkKAFIAIgBARIcCgZvZmZzZXQYAiABKAVCB7pIBBoCKABIAYgBARJJCghzdGF0dXNlcxgDIAMoDjImLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnRTdGF0dXNCD7pIDJIBCSIHggEEEAEgABITCgZjbG9zZWQYBCABKAhIAogBAUIICgZfbGltaXRCCQoHX29mZnNldEIJCgdfY2xvc2VkImcKFUxpc3RJbmNpZGVudHNSZXNwb25zZRI6CglpbmNpZGVudHMYASADKAsyJy5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkluY2lkZW50U3VtbWFyeRISCgp0b3RhbF9zaXplGAIgASgFIu4DChVVcGRhdGVJbmNpZGVudFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAESHgoFdGl0bGUYAiABKAlCCrpIB3IFEAEYgAJIAIgBARJLCghzZXZlcml0eRgDIAEoDjIoLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnRTZXZlcml0eUIKukgHggEEEAEgAEgBiAEBEiAKB3N1bW1hcnkYBCABKAlCCrpIB3IFEAEYoB9IAogBARIaCg1jbGVhcl9zdW1tYXJ5GAUgASgISAOIAQESJQoPY29tbWFuZGVyX2VtYWlsGAYgASgJQge6SARyAmABSASIAQESHAoPY2xlYXJfY29tbWFuZGVyGAcgASgISAWIAQESZAoKc3RhcnRlZF9hdBgIIAEoCUJLukhIckYyRF5cZHs0fS1cZHsyfS1cZHsyfVRcZHsyfTpcZHsyfTpcZHsyfShcLlxkezEsOX0pPyhafFsrLV1cZHsyfTpcZHsyfSkkSAaIAQFCCAoGX3RpdGxlQgsKCV9zZXZlcml0eUIKCghfc3VtbWFyeUIQCg5fY2xlYXJfc3VtbWFyeUISChBfY29tbWFuZGVyX2VtYWlsQhIKEF9jbGVhcl9jb21tYW5kZXJCDQoLX3N0YXJ0ZWRfYXQiTAoWVXBkYXRlSW5jaWRlbnRSZXNwb25zZRIyCghpbmNpZGVudBgBIAEoCzIgLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnQimQEKGFNldEluY2lkZW50U3RhdHVzUmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQARJCCgZzdGF0dXMYAiABKA4yJi5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkluY2lkZW50U3RhdHVzQgq6SAeCAQQQASAAEhsKBG5vdGUYAyABKAlCCLpIBXIDGJBOSACIAQFCBwoFX25vdGUiTwoZU2V0SW5jaWRlbnRTdGF0dXNSZXNwb25zZRIyCghpbmNpZGVudBgBIAEoCzIgLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnQiSgoWQWRkSW5jaWRlbnROb3RlUmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQARIbCgdtZXNzYWdlGAIgASgJQgq6SAdyBRABGJBOIk8KF0FkZEluY2lkZW50Tm90ZVJlc3BvbnNlEjQKBWV2ZW50GAEgASgLMiUub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudEV2ZW50IlEKF0xpbmtTdGF0dXNSZXBvcnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABEiEKEHN0YXR1c19yZXBvcnRfaWQYAiABKAlCB7pIBHICEAEiTgoYTGlua1N0YXR1c1JlcG9ydFJlc3BvbnNlEjIKCGluY2lkZW50GAEgASgLMiAub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudCIwChlVbmxpbmtTdGF0dXNSZXBvcnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIlAKGlVubGlua1N0YXR1c1JlcG9ydFJlc3BvbnNlEjIKCGluY2lkZW50GAEgASgLMiAub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudCJdChRDbG9zZUluY2lkZW50UmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQARIcCg9za2lwX3Bvc3Rtb3J0ZW0YAiABKAhIAIgBAUISChBfc2tpcF9wb3N0bW9ydGVtIksKFUNsb3NlSW5jaWRlbnRSZXNwb25zZRIyCghpbmNpZGVudBgBIAEoCzIgLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuSW5jaWRlbnQiLAoVRGVsZXRlSW5jaWRlbnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABIikKFkRlbGV0ZUluY2lkZW50UmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCCI0ChRHZXRQb3N0bW9ydGVtUmVxdWVzdBIcCgtpbmNpZGVudF9pZBgBIAEoCUIHukgEcgIQASJjChVHZXRQb3N0bW9ydGVtUmVzcG9uc2USOwoKcG9zdG1vcnRlbRgBIAEoCzIiLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuUG9zdG1vcnRlbUgAiAEBQg0KC19wb3N0bW9ydGVtIlUKF1VwZGF0ZVBvc3Rtb3J0ZW1SZXF1ZXN0EhwKC2luY2lkZW50X2lkGAEgASgJQge6SARyAhABEhwKB2NvbnRlbnQYAiABKAlCC7pICHIGEAEYoI0GIlIKGFVwZGF0ZVBvc3Rtb3J0ZW1SZXNwb25zZRI2Cgpwb3N0bW9ydGVtGAEgASgLMiIub3BlbnN0YXR1cy5pbmNpZGVudC52MS5Qb3N0bW9ydGVtIlYKGEFwcHJvdmVQb3N0bW9ydGVtUmVxdWVzdBIcCgtpbmNpZGVudF9pZBgBIAEoCUIHukgEcgIQARISCgVjbG9zZRgCIAEoCEgAiAEBQggKBl9jbG9zZSKHAQoZQXBwcm92ZVBvc3Rtb3J0ZW1SZXNwb25zZRI2Cgpwb3N0bW9ydGVtGAEgASgLMiIub3BlbnN0YXR1cy5pbmNpZGVudC52MS5Qb3N0bW9ydGVtEjIKCGluY2lkZW50GAIgASgLMiAub3BlbnN0YXR1cy5pbmNpZGVudC52MS5JbmNpZGVudDK1GgoPSW5jaWRlbnRTZXJ2aWNlEpUECg9EZWNsYXJlSW5jaWRlbnQSLi5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkRlY2xhcmVJbmNpZGVudFJlcXVlc3QaLy5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkRlY2xhcmVJbmNpZGVudFJlc3BvbnNlIqADukecAxqZA0RlY2xhcmVzIGEgbmV3IGluY2lkZW50IGluIHRoZSBvcGVuIHN0YXR1cy4gVGhlIGNvbW1hbmRlciBpcyBzZXQgYnkgZW1haWwgYW5kIG11c3QgYmUgYSBtZW1iZXIgb2YgdGhlIHdvcmtzcGFjZTsgd2l0aG91dCBvbmUgdGhlIGluY2lkZW50IGlzIHVuYXNzaWduZWQuIEEgbmV3bHkgYXNzaWduZWQgY29tbWFuZGVyIGlzIGVtYWlsZWQgdW5sZXNzIHRoZXkgb3duIHRoZSBBUEkga2V5LiBXaGVuIG9wZW5fc2xhY2tfY2hhbm5lbCBpcyB0cnVlIGFuZCBTbGFjayBpcyBjb25uZWN0ZWQsIGEgZGVkaWNhdGVkIGNoYW5uZWwgaXMgY3JlYXRlZCBhbmQgdGhlIHRlYW0gaW52aXRlZC4gc3RhcnRlZF9hdCBkZWZhdWx0cyB0byBub3cgYW5kIGNhbiBiZSBzZXQgaW4gdGhlIHBhc3QgZm9yIGEgcmV0cm9hY3RpdmUgZGVjbGFyZS4SawoLR2V0SW5jaWRlbnQSKi5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkdldEluY2lkZW50UmVxdWVzdBorLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuR2V0SW5jaWRlbnRSZXNwb25zZSIDkAIBEnEKDUxpc3RJbmNpZGVudHMSLC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkxpc3RJbmNpZGVudHNSZXF1ZXN0Gi0ub3BlbnN0YXR1cy5pbmNpZGVudC52MS5MaXN0SW5jaWRlbnRzUmVzcG9uc2UiA5ACARJvCg5VcGRhdGVJbmNpZGVudBItLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuVXBkYXRlSW5jaWRlbnRSZXF1ZXN0Gi4ub3BlbnN0YXR1cy5pbmNpZGVudC52MS5VcGRhdGVJbmNpZGVudFJlc3BvbnNlEskEChFTZXRJbmNpZGVudFN0YXR1cxIwLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuU2V0SW5jaWRlbnRTdGF0dXNSZXF1ZXN0GjEub3BlbnN0YXR1cy5pbmNpZGVudC52MS5TZXRJbmNpZGVudFN0YXR1c1Jlc3BvbnNlIs4DukfKAxrHA01vdmVzIGFuIGluY2lkZW50IHRvIGEgbmV3IHN0YXR1cy4gQWxsb3dlZCB0cmFuc2l0aW9uczogb3BlbiAtPiBtaXRpZ2F0ZWQsIHJlc29sdmVkIG9yIGNhbmNlbGVkOyBtaXRpZ2F0ZWQgLT4gcmVzb2x2ZWQsIG9wZW4gb3IgY2FuY2VsZWQ7IHJlc29sdmVkIC0+IG9wZW4uIENhbmNlbGVkIGlzIHRlcm1pbmFsIGFuZCBjbG9zZXMgdGhlIGluY2lkZW50LiBUaGUgc2FtZSBzdGF0dXMsIGEgZm9yYmlkZGVuIHRyYW5zaXRpb24gb3IgYSBjbG9zZWQgaW5jaWRlbnQgZmFpbCB3aXRoIGZhaWxlZF9wcmVjb25kaXRpb247IEluY2lkZW50LmFsbG93ZWRfdHJhbnNpdGlvbnMgbGlzdHMgd2hhdCBpcyB2YWxpZC4gQSBsaW5rZWQgc3RhdHVzIHJlcG9ydCBpcyBuZXZlciB1cGRhdGVkOiBwb3N0IHRoZSBwdWJsaWMgdXBkYXRlIHdpdGggU3RhdHVzUmVwb3J0U2VydmljZS5BZGRTdGF0dXNSZXBvcnRVcGRhdGUuEnIKD0FkZEluY2lkZW50Tm90ZRIuLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuQWRkSW5jaWRlbnROb3RlUmVxdWVzdBovLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuQWRkSW5jaWRlbnROb3RlUmVzcG9uc2USdQoQTGlua1N0YXR1c1JlcG9ydBIvLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuTGlua1N0YXR1c1JlcG9ydFJlcXVlc3QaMC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkxpbmtTdGF0dXNSZXBvcnRSZXNwb25zZRJ7ChJVbmxpbmtTdGF0dXNSZXBvcnQSMS5vcGVuc3RhdHVzLmluY2lkZW50LnYxLlVubGlua1N0YXR1c1JlcG9ydFJlcXVlc3QaMi5vcGVuc3RhdHVzLmluY2lkZW50LnYxLlVubGlua1N0YXR1c1JlcG9ydFJlc3BvbnNlErEDCg1DbG9zZUluY2lkZW50Eiwub3BlbnN0YXR1cy5pbmNpZGVudC52MS5DbG9zZUluY2lkZW50UmVxdWVzdBotLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuQ2xvc2VJbmNpZGVudFJlc3BvbnNlIsICuke+Ahq7AkNsb3NlcyBhIHJlc29sdmVkIGluY2lkZW50LCBhZnRlciB3aGljaCBvbmx5IGl0cyBwb3N0bW9ydGVtIGNhbiBjaGFuZ2UuIFJlcXVpcmVzIGFuIGFwcHJvdmVkIHBvc3Rtb3J0ZW0sIG9yIHNraXBfcG9zdG1vcnRlbSBzZXQgdG8gdHJ1ZS4gQWxsb3dlZCBmb3Igd29ya3NwYWNlIG93bmVycyBhbmQgYWRtaW5zIGFuZCBmb3IgdGhlIGluY2lkZW50IGNvbW1hbmRlci4gVGhlIEFQSSBrZXkgYWN0cyBhcyBpdHMgY3JlYXRvciwgd2l0aCB0aGUgY3JlYXRvcidzIGN1cnJlbnQgcm9sZTsga2V5cyB3aXRob3V0IGEgY3JlYXRvciBjYW5ub3QgY2FsbCB0aGlzLhK5AwoORGVsZXRlSW5jaWRlbnQSLS5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkRlbGV0ZUluY2lkZW50UmVxdWVzdBouLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuRGVsZXRlSW5jaWRlbnRSZXNwb25zZSLHArpHwwIawAJEZWxldGVzIGFuIGluY2lkZW50IGRlY2xhcmVkIGJ5IG1pc3Rha2UuIE9ubHkgYWxsb3dlZCB3aGlsZSB0aGUgaW5jaWRlbnQgaXMgb3BlbiBhbmQgd2FzIG5ldmVyIG1pdGlnYXRlZCwgcmVzb2x2ZWQgb3IgY2xvc2VkIChJbmNpZGVudC5kZWxldGFibGUpOyBjYW5jZWwgaXQgb3RoZXJ3aXNlLiBBbGxvd2VkIGZvciB3b3Jrc3BhY2Ugb3duZXJzIGFuZCBhZG1pbnMgb25seS4gVGhlIEFQSSBrZXkgYWN0cyBhcyBpdHMgY3JlYXRvciwgd2l0aCB0aGUgY3JlYXRvcidzIGN1cnJlbnQgcm9sZTsga2V5cyB3aXRob3V0IGEgY3JlYXRvciBjYW5ub3QgY2FsbCB0aGlzLhJxCg1HZXRQb3N0bW9ydGVtEiwub3BlbnN0YXR1cy5pbmNpZGVudC52MS5HZXRQb3N0bW9ydGVtUmVxdWVzdBotLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuR2V0UG9zdG1vcnRlbVJlc3BvbnNlIgOQAgESdQoQVXBkYXRlUG9zdG1vcnRlbRIvLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuVXBkYXRlUG9zdG1vcnRlbVJlcXVlc3QaMC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLlVwZGF0ZVBvc3Rtb3J0ZW1SZXNwb25zZRKqAwoRQXBwcm92ZVBvc3Rtb3J0ZW0SMC5vcGVuc3RhdHVzLmluY2lkZW50LnYxLkFwcHJvdmVQb3N0bW9ydGVtUmVxdWVzdBoxLm9wZW5zdGF0dXMuaW5jaWRlbnQudjEuQXBwcm92ZVBvc3Rtb3J0ZW1SZXNwb25zZSKvArpHqwIaqAJBcHByb3ZlcyB0aGUgZHJhZnQgcG9zdG1vcnRlbSBvZiBhIHJlc29sdmVkIGluY2lkZW50LiBXaXRoIGNsb3NlIHNldCB0byB0cnVlIHRoZSBpbmNpZGVudCBpcyBjbG9zZWQgaW4gdGhlIHNhbWUgc3RlcC4gQWxsb3dlZCBmb3Igd29ya3NwYWNlIG93bmVycyBhbmQgYWRtaW5zIGFuZCBmb3IgdGhlIGluY2lkZW50IGNvbW1hbmRlci4gVGhlIEFQSSBrZXkgYWN0cyBhcyBpdHMgY3JlYXRvciwgd2l0aCB0aGUgY3JlYXRvcidzIGN1cnJlbnQgcm9sZTsga2V5cyB3aXRob3V0IGEgY3JlYXRvciBjYW5ub3QgY2FsbCB0aGlzLkJVWlNnaXRodWIuY29tL29wZW5zdGF0dXNocS9vcGVuc3RhdHVzL3BhY2thZ2VzL3Byb3RvL29wZW5zdGF0dXMvaW5jaWRlbnQvdjE7aW5jaWRlbnR2MWIGcHJvdG8z", [file_buf_validate_validate, file_gnostic_openapi_v3_annotations, file_openstatus_incident_v1_incident]); + +/** + * DeclareIncidentRequest is the request to declare a new incident. + * + * @generated from message openstatus.incident.v1.DeclareIncidentRequest + */ +export type DeclareIncidentRequest = Message<"openstatus.incident.v1.DeclareIncidentRequest"> & { + /** + * Title of the incident (required, 1-256 characters). + * + * @generated from field: string title = 1; + */ + title: string; + + /** + * Severity of the incident (required). + * + * @generated from field: openstatus.incident.v1.IncidentSeverity severity = 2; + */ + severity: IncidentSeverity; + + /** + * Short human summary (optional, up to 4000 characters). + * + * @generated from field: optional string summary = 3; + */ + summary?: string | undefined; + + /** + * Email of the member who leads the response (optional, defaults to unassigned). + * + * @generated from field: optional string commander_email = 4; + */ + commanderEmail?: string | undefined; + + /** + * When the impact began (RFC 3339 format, optional, defaults to now). + * + * @generated from field: optional string started_at = 5; + */ + startedAt?: string | undefined; + + /** + * ID of a status report to link (optional). + * + * @generated from field: optional string status_report_id = 6; + */ + statusReportId?: string | undefined; + + /** + * Whether to open a Slack channel for the incident when Slack is connected (optional, defaults to false). + * + * @generated from field: optional bool open_slack_channel = 7; + */ + openSlackChannel?: boolean | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.DeclareIncidentRequest. + * Use `create(DeclareIncidentRequestSchema)` to create a new message. + */ +export const DeclareIncidentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 0); + +/** + * DeclareIncidentResponse is the response after declaring an incident. + * + * @generated from message openstatus.incident.v1.DeclareIncidentResponse + */ +export type DeclareIncidentResponse = Message<"openstatus.incident.v1.DeclareIncidentResponse"> & { + /** + * The declared incident. + * + * @generated from field: openstatus.incident.v1.Incident incident = 1; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.DeclareIncidentResponse. + * Use `create(DeclareIncidentResponseSchema)` to create a new message. + */ +export const DeclareIncidentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 1); + +/** + * GetIncidentRequest is the request to get an incident by ID. + * + * @generated from message openstatus.incident.v1.GetIncidentRequest + */ +export type GetIncidentRequest = Message<"openstatus.incident.v1.GetIncidentRequest"> & { + /** + * ID of the incident to retrieve (required). + * + * @generated from field: string id = 1; + */ + id: string; +}; + +/** + * Describes the message openstatus.incident.v1.GetIncidentRequest. + * Use `create(GetIncidentRequestSchema)` to create a new message. + */ +export const GetIncidentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 2); + +/** + * GetIncidentResponse is the response containing the incident and its timeline. + * + * @generated from message openstatus.incident.v1.GetIncidentResponse + */ +export type GetIncidentResponse = Message<"openstatus.incident.v1.GetIncidentResponse"> & { + /** + * The requested incident. + * + * @generated from field: openstatus.incident.v1.Incident incident = 1; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.GetIncidentResponse. + * Use `create(GetIncidentResponseSchema)` to create a new message. + */ +export const GetIncidentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 3); + +/** + * ListIncidentsRequest is the request to list incidents. + * + * @generated from message openstatus.incident.v1.ListIncidentsRequest + */ +export type ListIncidentsRequest = Message<"openstatus.incident.v1.ListIncidentsRequest"> & { + /** + * Maximum number of incidents to return (1-100, defaults to 50). + * + * @generated from field: optional int32 limit = 1; + */ + limit?: number | undefined; + + /** + * Number of incidents to skip for pagination (defaults to 0). + * + * @generated from field: optional int32 offset = 2; + */ + offset?: number | undefined; + + /** + * Filter by status (optional). If empty, returns all statuses. + * + * @generated from field: repeated openstatus.incident.v1.IncidentStatus statuses = 3; + */ + statuses: IncidentStatus[]; + + /** + * Filter by closed state (optional). If unset, returns both. + * + * @generated from field: optional bool closed = 4; + */ + closed?: boolean | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.ListIncidentsRequest. + * Use `create(ListIncidentsRequestSchema)` to create a new message. + */ +export const ListIncidentsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 4); + +/** + * ListIncidentsResponse is the response containing incident summaries. + * + * @generated from message openstatus.incident.v1.ListIncidentsResponse + */ +export type ListIncidentsResponse = Message<"openstatus.incident.v1.ListIncidentsResponse"> & { + /** + * List of incidents (metadata only, use GetIncident for full details). + * + * @generated from field: repeated openstatus.incident.v1.IncidentSummary incidents = 1; + */ + incidents: IncidentSummary[]; + + /** + * Total number of incidents matching the filter. + * + * @generated from field: int32 total_size = 2; + */ + totalSize: number; +}; + +/** + * Describes the message openstatus.incident.v1.ListIncidentsResponse. + * Use `create(ListIncidentsResponseSchema)` to create a new message. + */ +export const ListIncidentsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 5); + +/** + * UpdateIncidentRequest is the request to edit an incident. + * + * @generated from message openstatus.incident.v1.UpdateIncidentRequest + */ +export type UpdateIncidentRequest = Message<"openstatus.incident.v1.UpdateIncidentRequest"> & { + /** + * ID of the incident to update (required). + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * New title (optional, 1-256 characters). + * + * @generated from field: optional string title = 2; + */ + title?: string | undefined; + + /** + * New severity (optional). + * + * @generated from field: optional openstatus.incident.v1.IncidentSeverity severity = 3; + */ + severity?: IncidentSeverity | undefined; + + /** + * New summary (optional, 1-4000 characters). + * + * @generated from field: optional string summary = 4; + */ + summary?: string | undefined; + + /** + * Set to true to remove the summary. Cannot be combined with summary. + * + * @generated from field: optional bool clear_summary = 5; + */ + clearSummary?: boolean | undefined; + + /** + * Email of the new commander (optional). + * + * @generated from field: optional string commander_email = 6; + */ + commanderEmail?: string | undefined; + + /** + * Set to true to unassign the commander. Cannot be combined with commander_email. + * + * @generated from field: optional bool clear_commander = 7; + */ + clearCommander?: boolean | undefined; + + /** + * New start of impact (RFC 3339 format, optional). + * + * @generated from field: optional string started_at = 8; + */ + startedAt?: string | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.UpdateIncidentRequest. + * Use `create(UpdateIncidentRequestSchema)` to create a new message. + */ +export const UpdateIncidentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 6); + +/** + * UpdateIncidentResponse is the response after updating an incident. + * + * @generated from message openstatus.incident.v1.UpdateIncidentResponse + */ +export type UpdateIncidentResponse = Message<"openstatus.incident.v1.UpdateIncidentResponse"> & { + /** + * The updated incident (without its timeline). + * + * @generated from field: openstatus.incident.v1.Incident incident = 1; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.UpdateIncidentResponse. + * Use `create(UpdateIncidentResponseSchema)` to create a new message. + */ +export const UpdateIncidentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 7); + +/** + * SetIncidentStatusRequest is the request to change the status of an incident. + * + * @generated from message openstatus.incident.v1.SetIncidentStatusRequest + */ +export type SetIncidentStatusRequest = Message<"openstatus.incident.v1.SetIncidentStatusRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * Target status (required). + * + * @generated from field: openstatus.incident.v1.IncidentStatus status = 2; + */ + status: IncidentStatus; + + /** + * Note recorded with the change (optional, up to 10000 characters). + * + * @generated from field: optional string note = 3; + */ + note?: string | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.SetIncidentStatusRequest. + * Use `create(SetIncidentStatusRequestSchema)` to create a new message. + */ +export const SetIncidentStatusRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 8); + +/** + * SetIncidentStatusResponse is the response after changing the status of an incident. + * + * @generated from message openstatus.incident.v1.SetIncidentStatusResponse + */ +export type SetIncidentStatusResponse = Message<"openstatus.incident.v1.SetIncidentStatusResponse"> & { + /** + * The updated incident (without its timeline). + * + * @generated from field: openstatus.incident.v1.Incident incident = 1; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.SetIncidentStatusResponse. + * Use `create(SetIncidentStatusResponseSchema)` to create a new message. + */ +export const SetIncidentStatusResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 9); + +/** + * AddIncidentNoteRequest is the request to add a note to an incident timeline. + * + * @generated from message openstatus.incident.v1.AddIncidentNoteRequest + */ +export type AddIncidentNoteRequest = Message<"openstatus.incident.v1.AddIncidentNoteRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * Note text, markdown (required, 1-10000 characters). + * + * @generated from field: string message = 2; + */ + message: string; +}; + +/** + * Describes the message openstatus.incident.v1.AddIncidentNoteRequest. + * Use `create(AddIncidentNoteRequestSchema)` to create a new message. + */ +export const AddIncidentNoteRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 10); + +/** + * AddIncidentNoteResponse is the response after adding a note. + * + * @generated from message openstatus.incident.v1.AddIncidentNoteResponse + */ +export type AddIncidentNoteResponse = Message<"openstatus.incident.v1.AddIncidentNoteResponse"> & { + /** + * The timeline event that was added. + * + * @generated from field: openstatus.incident.v1.IncidentEvent event = 1; + */ + event?: IncidentEvent | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.AddIncidentNoteResponse. + * Use `create(AddIncidentNoteResponseSchema)` to create a new message. + */ +export const AddIncidentNoteResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 11); + +/** + * LinkStatusReportRequest is the request to link a status report to an incident. + * + * @generated from message openstatus.incident.v1.LinkStatusReportRequest + */ +export type LinkStatusReportRequest = Message<"openstatus.incident.v1.LinkStatusReportRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * ID of the status report to link (required). + * + * @generated from field: string status_report_id = 2; + */ + statusReportId: string; +}; + +/** + * Describes the message openstatus.incident.v1.LinkStatusReportRequest. + * Use `create(LinkStatusReportRequestSchema)` to create a new message. + */ +export const LinkStatusReportRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 12); + +/** + * LinkStatusReportResponse is the response after linking a status report. + * + * @generated from message openstatus.incident.v1.LinkStatusReportResponse + */ +export type LinkStatusReportResponse = Message<"openstatus.incident.v1.LinkStatusReportResponse"> & { + /** + * The updated incident (without its timeline). + * + * @generated from field: openstatus.incident.v1.Incident incident = 1; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.LinkStatusReportResponse. + * Use `create(LinkStatusReportResponseSchema)` to create a new message. + */ +export const LinkStatusReportResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 13); + +/** + * UnlinkStatusReportRequest is the request to unlink the status report of an incident. + * + * @generated from message openstatus.incident.v1.UnlinkStatusReportRequest + */ +export type UnlinkStatusReportRequest = Message<"openstatus.incident.v1.UnlinkStatusReportRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string id = 1; + */ + id: string; +}; + +/** + * Describes the message openstatus.incident.v1.UnlinkStatusReportRequest. + * Use `create(UnlinkStatusReportRequestSchema)` to create a new message. + */ +export const UnlinkStatusReportRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 14); + +/** + * UnlinkStatusReportResponse is the response after unlinking the status report. + * + * @generated from message openstatus.incident.v1.UnlinkStatusReportResponse + */ +export type UnlinkStatusReportResponse = Message<"openstatus.incident.v1.UnlinkStatusReportResponse"> & { + /** + * The updated incident (without its timeline). + * + * @generated from field: openstatus.incident.v1.Incident incident = 1; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.UnlinkStatusReportResponse. + * Use `create(UnlinkStatusReportResponseSchema)` to create a new message. + */ +export const UnlinkStatusReportResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 15); + +/** + * CloseIncidentRequest is the request to close a resolved incident. + * + * @generated from message openstatus.incident.v1.CloseIncidentRequest + */ +export type CloseIncidentRequest = Message<"openstatus.incident.v1.CloseIncidentRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string id = 1; + */ + id: string; + + /** + * Close without an approved postmortem (optional, defaults to false). + * + * @generated from field: optional bool skip_postmortem = 2; + */ + skipPostmortem?: boolean | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.CloseIncidentRequest. + * Use `create(CloseIncidentRequestSchema)` to create a new message. + */ +export const CloseIncidentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 16); + +/** + * CloseIncidentResponse is the response after closing an incident. + * + * @generated from message openstatus.incident.v1.CloseIncidentResponse + */ +export type CloseIncidentResponse = Message<"openstatus.incident.v1.CloseIncidentResponse"> & { + /** + * The closed incident (without its timeline). + * + * @generated from field: openstatus.incident.v1.Incident incident = 1; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.CloseIncidentResponse. + * Use `create(CloseIncidentResponseSchema)` to create a new message. + */ +export const CloseIncidentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 17); + +/** + * DeleteIncidentRequest is the request to delete an incident. + * + * @generated from message openstatus.incident.v1.DeleteIncidentRequest + */ +export type DeleteIncidentRequest = Message<"openstatus.incident.v1.DeleteIncidentRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string id = 1; + */ + id: string; +}; + +/** + * Describes the message openstatus.incident.v1.DeleteIncidentRequest. + * Use `create(DeleteIncidentRequestSchema)` to create a new message. + */ +export const DeleteIncidentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 18); + +/** + * DeleteIncidentResponse is the response after deleting an incident. + * + * @generated from message openstatus.incident.v1.DeleteIncidentResponse + */ +export type DeleteIncidentResponse = Message<"openstatus.incident.v1.DeleteIncidentResponse"> & { + /** + * Whether the deletion was successful. + * + * @generated from field: bool success = 1; + */ + success: boolean; +}; + +/** + * Describes the message openstatus.incident.v1.DeleteIncidentResponse. + * Use `create(DeleteIncidentResponseSchema)` to create a new message. + */ +export const DeleteIncidentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 19); + +/** + * GetPostmortemRequest is the request to get the postmortem of an incident. + * + * @generated from message openstatus.incident.v1.GetPostmortemRequest + */ +export type GetPostmortemRequest = Message<"openstatus.incident.v1.GetPostmortemRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string incident_id = 1; + */ + incidentId: string; +}; + +/** + * Describes the message openstatus.incident.v1.GetPostmortemRequest. + * Use `create(GetPostmortemRequestSchema)` to create a new message. + */ +export const GetPostmortemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 20); + +/** + * GetPostmortemResponse is the response containing the postmortem, if any. + * + * @generated from message openstatus.incident.v1.GetPostmortemResponse + */ +export type GetPostmortemResponse = Message<"openstatus.incident.v1.GetPostmortemResponse"> & { + /** + * The postmortem (unset when none has been written yet). + * + * @generated from field: optional openstatus.incident.v1.Postmortem postmortem = 1; + */ + postmortem?: Postmortem | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.GetPostmortemResponse. + * Use `create(GetPostmortemResponseSchema)` to create a new message. + */ +export const GetPostmortemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 21); + +/** + * UpdatePostmortemRequest is the request to write the postmortem of a resolved incident. + * + * @generated from message openstatus.incident.v1.UpdatePostmortemRequest + */ +export type UpdatePostmortemRequest = Message<"openstatus.incident.v1.UpdatePostmortemRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string incident_id = 1; + */ + incidentId: string; + + /** + * Markdown body (required, 1-100000 characters). Replaces the current body; an approved postmortem stays approved. + * + * @generated from field: string content = 2; + */ + content: string; +}; + +/** + * Describes the message openstatus.incident.v1.UpdatePostmortemRequest. + * Use `create(UpdatePostmortemRequestSchema)` to create a new message. + */ +export const UpdatePostmortemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 22); + +/** + * UpdatePostmortemResponse is the response after writing the postmortem. + * + * @generated from message openstatus.incident.v1.UpdatePostmortemResponse + */ +export type UpdatePostmortemResponse = Message<"openstatus.incident.v1.UpdatePostmortemResponse"> & { + /** + * The saved postmortem. + * + * @generated from field: openstatus.incident.v1.Postmortem postmortem = 1; + */ + postmortem?: Postmortem | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.UpdatePostmortemResponse. + * Use `create(UpdatePostmortemResponseSchema)` to create a new message. + */ +export const UpdatePostmortemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 23); + +/** + * ApprovePostmortemRequest is the request to approve the postmortem of an incident. + * + * @generated from message openstatus.incident.v1.ApprovePostmortemRequest + */ +export type ApprovePostmortemRequest = Message<"openstatus.incident.v1.ApprovePostmortemRequest"> & { + /** + * ID of the incident (required). + * + * @generated from field: string incident_id = 1; + */ + incidentId: string; + + /** + * Also close the incident (optional, defaults to false). + * + * @generated from field: optional bool close = 2; + */ + close?: boolean | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.ApprovePostmortemRequest. + * Use `create(ApprovePostmortemRequestSchema)` to create a new message. + */ +export const ApprovePostmortemRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 24); + +/** + * ApprovePostmortemResponse is the response after approving the postmortem. + * + * @generated from message openstatus.incident.v1.ApprovePostmortemResponse + */ +export type ApprovePostmortemResponse = Message<"openstatus.incident.v1.ApprovePostmortemResponse"> & { + /** + * The approved postmortem. + * + * @generated from field: openstatus.incident.v1.Postmortem postmortem = 1; + */ + postmortem?: Postmortem | undefined; + + /** + * The incident after approval (without its timeline). + * + * @generated from field: openstatus.incident.v1.Incident incident = 2; + */ + incident?: Incident | undefined; +}; + +/** + * Describes the message openstatus.incident.v1.ApprovePostmortemResponse. + * Use `create(ApprovePostmortemResponseSchema)` to create a new message. + */ +export const ApprovePostmortemResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_openstatus_incident_v1_service, 25); + +/** + * IncidentService declares and runs incidents: lifecycle, timeline, status report link and postmortem. + * + * @generated from service openstatus.incident.v1.IncidentService + */ +export const IncidentService: GenService<{ + /** + * DeclareIncident declares a new incident. + * + * @generated from rpc openstatus.incident.v1.IncidentService.DeclareIncident + */ + declareIncident: { + methodKind: "unary"; + input: typeof DeclareIncidentRequestSchema; + output: typeof DeclareIncidentResponseSchema; + }, + /** + * GetIncident retrieves an incident by ID, including its timeline. + * + * @generated from rpc openstatus.incident.v1.IncidentService.GetIncident + */ + getIncident: { + methodKind: "unary"; + input: typeof GetIncidentRequestSchema; + output: typeof GetIncidentResponseSchema; + }, + /** + * ListIncidents returns the incidents of the workspace (metadata only), open first. + * + * @generated from rpc openstatus.incident.v1.IncidentService.ListIncidents + */ + listIncidents: { + methodKind: "unary"; + input: typeof ListIncidentsRequestSchema; + output: typeof ListIncidentsResponseSchema; + }, + /** + * UpdateIncident edits the title, severity, summary, commander or start time of an incident. + * + * @generated from rpc openstatus.incident.v1.IncidentService.UpdateIncident + */ + updateIncident: { + methodKind: "unary"; + input: typeof UpdateIncidentRequestSchema; + output: typeof UpdateIncidentResponseSchema; + }, + /** + * SetIncidentStatus moves an incident through its lifecycle, optionally with a note. + * + * @generated from rpc openstatus.incident.v1.IncidentService.SetIncidentStatus + */ + setIncidentStatus: { + methodKind: "unary"; + input: typeof SetIncidentStatusRequestSchema; + output: typeof SetIncidentStatusResponseSchema; + }, + /** + * AddIncidentNote appends a note to the incident timeline. + * + * @generated from rpc openstatus.incident.v1.IncidentService.AddIncidentNote + */ + addIncidentNote: { + methodKind: "unary"; + input: typeof AddIncidentNoteRequestSchema; + output: typeof AddIncidentNoteResponseSchema; + }, + /** + * LinkStatusReport links a public status report to the incident. + * + * @generated from rpc openstatus.incident.v1.IncidentService.LinkStatusReport + */ + linkStatusReport: { + methodKind: "unary"; + input: typeof LinkStatusReportRequestSchema; + output: typeof LinkStatusReportResponseSchema; + }, + /** + * UnlinkStatusReport removes the link to the incident's status report. + * + * @generated from rpc openstatus.incident.v1.IncidentService.UnlinkStatusReport + */ + unlinkStatusReport: { + methodKind: "unary"; + input: typeof UnlinkStatusReportRequestSchema; + output: typeof UnlinkStatusReportResponseSchema; + }, + /** + * CloseIncident closes a resolved incident. + * + * @generated from rpc openstatus.incident.v1.IncidentService.CloseIncident + */ + closeIncident: { + methodKind: "unary"; + input: typeof CloseIncidentRequestSchema; + output: typeof CloseIncidentResponseSchema; + }, + /** + * DeleteIncident removes an incident declared by mistake. + * + * @generated from rpc openstatus.incident.v1.IncidentService.DeleteIncident + */ + deleteIncident: { + methodKind: "unary"; + input: typeof DeleteIncidentRequestSchema; + output: typeof DeleteIncidentResponseSchema; + }, + /** + * GetPostmortem retrieves the postmortem of an incident, if any. + * + * @generated from rpc openstatus.incident.v1.IncidentService.GetPostmortem + */ + getPostmortem: { + methodKind: "unary"; + input: typeof GetPostmortemRequestSchema; + output: typeof GetPostmortemResponseSchema; + }, + /** + * UpdatePostmortem creates or replaces the postmortem body of a resolved incident. + * + * @generated from rpc openstatus.incident.v1.IncidentService.UpdatePostmortem + */ + updatePostmortem: { + methodKind: "unary"; + input: typeof UpdatePostmortemRequestSchema; + output: typeof UpdatePostmortemResponseSchema; + }, + /** + * ApprovePostmortem approves the postmortem and optionally closes the incident. + * + * @generated from rpc openstatus.incident.v1.IncidentService.ApprovePostmortem + */ + approvePostmortem: { + methodKind: "unary"; + input: typeof ApprovePostmortemRequestSchema; + output: typeof ApprovePostmortemResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_openstatus_incident_v1_service, 0); + diff --git a/packages/proto/gen/ts/openstatus/status_report/v1/service_pb.ts b/packages/proto/gen/ts/openstatus/status_report/v1/service_pb.ts index b8f76554..aa35d523 100644 --- a/packages/proto/gen/ts/openstatus/status_report/v1/service_pb.ts +++ b/packages/proto/gen/ts/openstatus/status_report/v1/service_pb.ts @@ -14,7 +14,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file openstatus/status_report/v1/service.proto. */ export const file_openstatus_status_report_v1_service: GenFile = /*@__PURE__*/ - fileDesc("CilvcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjEvc2VydmljZS5wcm90bxIbb3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxIpAEChlDcmVhdGVTdGF0dXNSZXBvcnRSZXF1ZXN0EjoKBXRpdGxlGAEgASgJQiu6RyE6HxIdQVBJIERlZ3JhZGF0aW9uIEludmVzdGlnYXRpb266SARyAhABEkkKBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXNCCLpIBYIBAhABElUKB21lc3NhZ2UYAyABKAlCRLpHOjo4EjZXZSBhcmUgaW52ZXN0aWdhdGluZyByZXBvcnRzIG9mIGluY3JlYXNlZCBBUEkgbGF0ZW5jeS66SARyAhABEnYKBGRhdGUYBCABKAlCaLpHGjoYEhYiMjAyNC0wMy0xNVQxMDozMDowMFoiukhIckYyRF5cZHs0fS1cZHsyfS1cZHsyfVRcZHsyfTpcZHsyfTpcZHsyfShcLlxkezEsOX0pPyhafFsrLV1cZHsyfTpcZHsyfSkkEhgKB3BhZ2VfaWQYBSABKAlCB7pIBHICEAESGgoScGFnZV9jb21wb25lbnRfaWRzGAYgAygJEhMKBm5vdGlmeRgHIAEoCEgAiAEBEkcKEWNvbXBvbmVudF9pbXBhY3RzGAggAygLMiwub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkNvbXBvbmVudEltcGFjdEIJCgdfbm90aWZ5Il4KGkNyZWF0ZVN0YXR1c1JlcG9ydFJlc3BvbnNlEkAKDXN0YXR1c19yZXBvcnQYASABKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0Ii0KFkdldFN0YXR1c1JlcG9ydFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAEiWwoXR2V0U3RhdHVzUmVwb3J0UmVzcG9uc2USQAoNc3RhdHVzX3JlcG9ydBgBIAEoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnQirwEKGExpc3RTdGF0dXNSZXBvcnRzUmVxdWVzdBIdCgVsaW1pdBgBIAEoBUIJukgGGgQYZCgBSACIAQESHAoGb2Zmc2V0GAIgASgFQge6SAQaAigASAGIAQESQQoIc3RhdHVzZXMYAyADKA4yLy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0U3RhdHVzQggKBl9saW1pdEIJCgdfb2Zmc2V0InkKGUxpc3RTdGF0dXNSZXBvcnRzUmVzcG9uc2USSAoOc3RhdHVzX3JlcG9ydHMYASADKAsyMC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0U3VtbWFyeRISCgp0b3RhbF9zaXplGAIgASgFIrABChlVcGRhdGVTdGF0dXNSZXBvcnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABEhIKBXRpdGxlGAIgASgJSACIAQESGgoScGFnZV9jb21wb25lbnRfaWRzGAMgAygJEiYKGXVwZGF0ZV9wYWdlX2NvbXBvbmVudF9pZHMYBCABKAhIAYgBAUIICgZfdGl0bGVCHAoaX3VwZGF0ZV9wYWdlX2NvbXBvbmVudF9pZHMiXgoaVXBkYXRlU3RhdHVzUmVwb3J0UmVzcG9uc2USQAoNc3RhdHVzX3JlcG9ydBgBIAEoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnQiMAoZRGVsZXRlU3RhdHVzUmVwb3J0UmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQASItChpEZWxldGVTdGF0dXNSZXBvcnRSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIvgCChxBZGRTdGF0dXNSZXBvcnRVcGRhdGVSZXF1ZXN0EiEKEHN0YXR1c19yZXBvcnRfaWQYASABKAlCB7pIBHICEAESSQoGc3RhdHVzGAIgASgOMi8ub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlN0YXR1c1JlcG9ydFN0YXR1c0IIukgFggECEAESGAoHbWVzc2FnZRgDIAEoCUIHukgEcgIQARJeCgRkYXRlGAQgASgJQku6SEhyRjJEXlxkezR9LVxkezJ9LVxkezJ9VFxkezJ9OlxkezJ9OlxkezJ9KFwuXGR7MSw5fSk/KFp8WystXVxkezJ9OlxkezJ9KSRIAIgBARITCgZub3RpZnkYBSABKAhIAYgBARJHChFjb21wb25lbnRfaW1wYWN0cxgGIAMoCzIsLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5Db21wb25lbnRJbXBhY3RCBwoFX2RhdGVCCQoHX25vdGlmeSJhCh1BZGRTdGF0dXNSZXBvcnRVcGRhdGVSZXNwb25zZRJACg1zdGF0dXNfcmVwb3J0GAEgASgLMikub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlN0YXR1c1JlcG9ydDK/CwoTU3RhdHVzUmVwb3J0U2VydmljZRLOAwoSQ3JlYXRlU3RhdHVzUmVwb3J0EjYub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkNyZWF0ZVN0YXR1c1JlcG9ydFJlcXVlc3QaNy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuQ3JlYXRlU3RhdHVzUmVwb3J0UmVzcG9uc2UixgK6R8ICGr8CQ3JlYXRlcyBhIG5ldyBzdGF0dXMgcmVwb3J0IHdpdGggYW4gaW5pdGlhbCB1cGRhdGUgZW50cnkuIFRoZSByZXBvcnQgaXMgYXNzb2NpYXRlZCB3aXRoIGEgc3RhdHVzIHBhZ2UgYW5kIG9wdGlvbmFsbHkgc3BlY2lmaWMgcGFnZSBjb21wb25lbnRzLiBBbiBpbml0aWFsIFN0YXR1c1JlcG9ydFVwZGF0ZSBpcyBjcmVhdGVkIGF1dG9tYXRpY2FsbHkgd2l0aCB0aGUgcHJvdmlkZWQgc3RhdHVzLCBtZXNzYWdlLCBhbmQgZGF0ZS4gSWYgbm90aWZ5IGlzIHRydWUsIHN1YnNjcmliZXJzIG9mIHRoZSBhc3NvY2lhdGVkIHBhZ2UgYXJlIG5vdGlmaWVkIGJ5IGVtYWlsLhKBAQoPR2V0U3RhdHVzUmVwb3J0EjMub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkdldFN0YXR1c1JlcG9ydFJlcXVlc3QaNC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuR2V0U3RhdHVzUmVwb3J0UmVzcG9uc2UiA5ACARKHAQoRTGlzdFN0YXR1c1JlcG9ydHMSNS5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuTGlzdFN0YXR1c1JlcG9ydHNSZXF1ZXN0GjYub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkxpc3RTdGF0dXNSZXBvcnRzUmVzcG9uc2UiA5ACARKFAQoSVXBkYXRlU3RhdHVzUmVwb3J0EjYub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlVwZGF0ZVN0YXR1c1JlcG9ydFJlcXVlc3QaNy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuVXBkYXRlU3RhdHVzUmVwb3J0UmVzcG9uc2UShQEKEkRlbGV0ZVN0YXR1c1JlcG9ydBI2Lm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5EZWxldGVTdGF0dXNSZXBvcnRSZXF1ZXN0Gjcub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkRlbGV0ZVN0YXR1c1JlcG9ydFJlc3BvbnNlErgDChVBZGRTdGF0dXNSZXBvcnRVcGRhdGUSOS5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuQWRkU3RhdHVzUmVwb3J0VXBkYXRlUmVxdWVzdBo6Lm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5BZGRTdGF0dXNSZXBvcnRVcGRhdGVSZXNwb25zZSKnArpHowIaoAJBZGRzIGEgbmV3IHVwZGF0ZSBlbnRyeSB0byBhbiBleGlzdGluZyBzdGF0dXMgcmVwb3J0IGFuZCB0cmFuc2l0aW9ucyB0aGUgcmVwb3J0IHRvIHRoZSBzcGVjaWZpZWQgc3RhdHVzLiBTdGF0dXMgcmVwb3J0cyBmb2xsb3cgYSBsaWZlY3ljbGU6IGludmVzdGlnYXRpbmcgLT4gaWRlbnRpZmllZCAtPiBtb25pdG9yaW5nIC0+IHJlc29sdmVkLiBJZiBub3RpZnkgaXMgdHJ1ZSwgc3Vic2NyaWJlcnMgb2YgdGhlIGFzc29jaWF0ZWQgcGFnZSBhcmUgbm90aWZpZWQgYnkgZW1haWwgYWJvdXQgdGhlIHVwZGF0ZS5CXlpcZ2l0aHViLmNvbS9vcGVuc3RhdHVzaHEvb3BlbnN0YXR1cy9wYWNrYWdlcy9wcm90by9vcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjE7c3RhdHVzcmVwb3J0djFiBnByb3RvMw", [file_buf_validate_validate, file_gnostic_openapi_v3_annotations, file_openstatus_status_report_v1_status_report]); + fileDesc("CilvcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjEvc2VydmljZS5wcm90bxIbb3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxIsMEChlDcmVhdGVTdGF0dXNSZXBvcnRSZXF1ZXN0EjoKBXRpdGxlGAEgASgJQiu6RyE6HxIdQVBJIERlZ3JhZGF0aW9uIEludmVzdGlnYXRpb266SARyAhABEkkKBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXNCCLpIBYIBAhABElUKB21lc3NhZ2UYAyABKAlCRLpHOjo4EjZXZSBhcmUgaW52ZXN0aWdhdGluZyByZXBvcnRzIG9mIGluY3JlYXNlZCBBUEkgbGF0ZW5jeS66SARyAhABEnYKBGRhdGUYBCABKAlCaLpHGjoYEhYiMjAyNC0wMy0xNVQxMDozMDowMFoiukhIckYyRF5cZHs0fS1cZHsyfS1cZHsyfVRcZHsyfTpcZHsyfTpcZHsyfShcLlxkezEsOX0pPyhafFsrLV1cZHsyfTpcZHsyfSkkEhgKB3BhZ2VfaWQYBSABKAlCB7pIBHICEAESGgoScGFnZV9jb21wb25lbnRfaWRzGAYgAygJEhMKBm5vdGlmeRgHIAEoCEgAiAEBEkcKEWNvbXBvbmVudF9pbXBhY3RzGAggAygLMiwub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkNvbXBvbmVudEltcGFjdBIhCgtpbmNpZGVudF9pZBgJIAEoCUIHukgEcgIQAUgBiAEBQgkKB19ub3RpZnlCDgoMX2luY2lkZW50X2lkIl4KGkNyZWF0ZVN0YXR1c1JlcG9ydFJlc3BvbnNlEkAKDXN0YXR1c19yZXBvcnQYASABKAsyKS5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0Ii0KFkdldFN0YXR1c1JlcG9ydFJlcXVlc3QSEwoCaWQYASABKAlCB7pIBHICEAEiWwoXR2V0U3RhdHVzUmVwb3J0UmVzcG9uc2USQAoNc3RhdHVzX3JlcG9ydBgBIAEoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnQirwEKGExpc3RTdGF0dXNSZXBvcnRzUmVxdWVzdBIdCgVsaW1pdBgBIAEoBUIJukgGGgQYZCgBSACIAQESHAoGb2Zmc2V0GAIgASgFQge6SAQaAigASAGIAQESQQoIc3RhdHVzZXMYAyADKA4yLy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0U3RhdHVzQggKBl9saW1pdEIJCgdfb2Zmc2V0InkKGUxpc3RTdGF0dXNSZXBvcnRzUmVzcG9uc2USSAoOc3RhdHVzX3JlcG9ydHMYASADKAsyMC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0U3VtbWFyeRISCgp0b3RhbF9zaXplGAIgASgFIrABChlVcGRhdGVTdGF0dXNSZXBvcnRSZXF1ZXN0EhMKAmlkGAEgASgJQge6SARyAhABEhIKBXRpdGxlGAIgASgJSACIAQESGgoScGFnZV9jb21wb25lbnRfaWRzGAMgAygJEiYKGXVwZGF0ZV9wYWdlX2NvbXBvbmVudF9pZHMYBCABKAhIAYgBAUIICgZfdGl0bGVCHAoaX3VwZGF0ZV9wYWdlX2NvbXBvbmVudF9pZHMiXgoaVXBkYXRlU3RhdHVzUmVwb3J0UmVzcG9uc2USQAoNc3RhdHVzX3JlcG9ydBgBIAEoCzIpLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnQiMAoZRGVsZXRlU3RhdHVzUmVwb3J0UmVxdWVzdBITCgJpZBgBIAEoCUIHukgEcgIQASItChpEZWxldGVTdGF0dXNSZXBvcnRSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIIvgCChxBZGRTdGF0dXNSZXBvcnRVcGRhdGVSZXF1ZXN0EiEKEHN0YXR1c19yZXBvcnRfaWQYASABKAlCB7pIBHICEAESSQoGc3RhdHVzGAIgASgOMi8ub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlN0YXR1c1JlcG9ydFN0YXR1c0IIukgFggECEAESGAoHbWVzc2FnZRgDIAEoCUIHukgEcgIQARJeCgRkYXRlGAQgASgJQku6SEhyRjJEXlxkezR9LVxkezJ9LVxkezJ9VFxkezJ9OlxkezJ9OlxkezJ9KFwuXGR7MSw5fSk/KFp8WystXVxkezJ9OlxkezJ9KSRIAIgBARITCgZub3RpZnkYBSABKAhIAYgBARJHChFjb21wb25lbnRfaW1wYWN0cxgGIAMoCzIsLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5Db21wb25lbnRJbXBhY3RCBwoFX2RhdGVCCQoHX25vdGlmeSJhCh1BZGRTdGF0dXNSZXBvcnRVcGRhdGVSZXNwb25zZRJACg1zdGF0dXNfcmVwb3J0GAEgASgLMikub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlN0YXR1c1JlcG9ydDK/CwoTU3RhdHVzUmVwb3J0U2VydmljZRLOAwoSQ3JlYXRlU3RhdHVzUmVwb3J0EjYub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkNyZWF0ZVN0YXR1c1JlcG9ydFJlcXVlc3QaNy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuQ3JlYXRlU3RhdHVzUmVwb3J0UmVzcG9uc2UixgK6R8ICGr8CQ3JlYXRlcyBhIG5ldyBzdGF0dXMgcmVwb3J0IHdpdGggYW4gaW5pdGlhbCB1cGRhdGUgZW50cnkuIFRoZSByZXBvcnQgaXMgYXNzb2NpYXRlZCB3aXRoIGEgc3RhdHVzIHBhZ2UgYW5kIG9wdGlvbmFsbHkgc3BlY2lmaWMgcGFnZSBjb21wb25lbnRzLiBBbiBpbml0aWFsIFN0YXR1c1JlcG9ydFVwZGF0ZSBpcyBjcmVhdGVkIGF1dG9tYXRpY2FsbHkgd2l0aCB0aGUgcHJvdmlkZWQgc3RhdHVzLCBtZXNzYWdlLCBhbmQgZGF0ZS4gSWYgbm90aWZ5IGlzIHRydWUsIHN1YnNjcmliZXJzIG9mIHRoZSBhc3NvY2lhdGVkIHBhZ2UgYXJlIG5vdGlmaWVkIGJ5IGVtYWlsLhKBAQoPR2V0U3RhdHVzUmVwb3J0EjMub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkdldFN0YXR1c1JlcG9ydFJlcXVlc3QaNC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuR2V0U3RhdHVzUmVwb3J0UmVzcG9uc2UiA5ACARKHAQoRTGlzdFN0YXR1c1JlcG9ydHMSNS5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuTGlzdFN0YXR1c1JlcG9ydHNSZXF1ZXN0GjYub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkxpc3RTdGF0dXNSZXBvcnRzUmVzcG9uc2UiA5ACARKFAQoSVXBkYXRlU3RhdHVzUmVwb3J0EjYub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLlVwZGF0ZVN0YXR1c1JlcG9ydFJlcXVlc3QaNy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuVXBkYXRlU3RhdHVzUmVwb3J0UmVzcG9uc2UShQEKEkRlbGV0ZVN0YXR1c1JlcG9ydBI2Lm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5EZWxldGVTdGF0dXNSZXBvcnRSZXF1ZXN0Gjcub3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxLkRlbGV0ZVN0YXR1c1JlcG9ydFJlc3BvbnNlErgDChVBZGRTdGF0dXNSZXBvcnRVcGRhdGUSOS5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuQWRkU3RhdHVzUmVwb3J0VXBkYXRlUmVxdWVzdBo6Lm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5BZGRTdGF0dXNSZXBvcnRVcGRhdGVSZXNwb25zZSKnArpHowIaoAJBZGRzIGEgbmV3IHVwZGF0ZSBlbnRyeSB0byBhbiBleGlzdGluZyBzdGF0dXMgcmVwb3J0IGFuZCB0cmFuc2l0aW9ucyB0aGUgcmVwb3J0IHRvIHRoZSBzcGVjaWZpZWQgc3RhdHVzLiBTdGF0dXMgcmVwb3J0cyBmb2xsb3cgYSBsaWZlY3ljbGU6IGludmVzdGlnYXRpbmcgLT4gaWRlbnRpZmllZCAtPiBtb25pdG9yaW5nIC0+IHJlc29sdmVkLiBJZiBub3RpZnkgaXMgdHJ1ZSwgc3Vic2NyaWJlcnMgb2YgdGhlIGFzc29jaWF0ZWQgcGFnZSBhcmUgbm90aWZpZWQgYnkgZW1haWwgYWJvdXQgdGhlIHVwZGF0ZS5CXlpcZ2l0aHViLmNvbS9vcGVuc3RhdHVzaHEvb3BlbnN0YXR1cy9wYWNrYWdlcy9wcm90by9vcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjE7c3RhdHVzcmVwb3J0djFiBnByb3RvMw", [file_buf_validate_validate, file_gnostic_openapi_v3_annotations, file_openstatus_status_report_v1_status_report]); /** * CreateStatusReportRequest is the request to create a new status report. @@ -79,6 +79,14 @@ export type CreateStatusReportRequest = Message<"openstatus.status_report.v1.Cre * @generated from field: repeated openstatus.status_report.v1.ComponentImpact component_impacts = 8; */ componentImpacts: ComponentImpact[]; + + /** + * ID of an incident to link the report to (optional). Linked in the same + * step: a closed incident, or one already linked to a report, fails the create. + * + * @generated from field: optional string incident_id = 9; + */ + incidentId?: string | undefined; }; /** diff --git a/packages/proto/gen/ts/openstatus/status_report/v1/status_report_pb.ts b/packages/proto/gen/ts/openstatus/status_report/v1/status_report_pb.ts index 95ead3c1..4ddf721d 100644 --- a/packages/proto/gen/ts/openstatus/status_report/v1/status_report_pb.ts +++ b/packages/proto/gen/ts/openstatus/status_report/v1/status_report_pb.ts @@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file openstatus/status_report/v1/status_report.proto. */ export const file_openstatus_status_report_v1_status_report: GenFile = /*@__PURE__*/ - fileDesc("Ci9vcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjEvc3RhdHVzX3JlcG9ydC5wcm90bxIbb3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxIm4KD0NvbXBvbmVudEltcGFjdBIZChFwYWdlX2NvbXBvbmVudF9pZBgBIAEoCRJACgZpbXBhY3QYAiABKA4yMC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuUGFnZUNvbXBvbmVudEltcGFjdCLdAQoSU3RhdHVzUmVwb3J0VXBkYXRlEgoKAmlkGAEgASgJEj8KBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXMSDAoEZGF0ZRgDIAEoCRIPCgdtZXNzYWdlGAQgASgJEhIKCmNyZWF0ZWRfYXQYBSABKAkSRwoRY29tcG9uZW50X2ltcGFjdHMYBiADKAsyLC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuQ29tcG9uZW50SW1wYWN0IrUBChNTdGF0dXNSZXBvcnRTdW1tYXJ5EgoKAmlkGAEgASgJEj8KBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXMSDQoFdGl0bGUYAyABKAkSGgoScGFnZV9jb21wb25lbnRfaWRzGAQgAygJEhIKCmNyZWF0ZWRfYXQYBSABKAkSEgoKdXBkYXRlZF9hdBgGIAEoCSLwAQoMU3RhdHVzUmVwb3J0EgoKAmlkGAEgASgJEj8KBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXMSDQoFdGl0bGUYAyABKAkSGgoScGFnZV9jb21wb25lbnRfaWRzGAQgAygJEkAKB3VwZGF0ZXMYBSADKAsyLy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0VXBkYXRlEhIKCmNyZWF0ZWRfYXQYBiABKAkSEgoKdXBkYXRlZF9hdBgHIAEoCSrPAQoSU3RhdHVzUmVwb3J0U3RhdHVzEiQKIFNUQVRVU19SRVBPUlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASJgoiU1RBVFVTX1JFUE9SVF9TVEFUVVNfSU5WRVNUSUdBVElORxABEiMKH1NUQVRVU19SRVBPUlRfU1RBVFVTX0lERU5USUZJRUQQAhIjCh9TVEFUVVNfUkVQT1JUX1NUQVRVU19NT05JVE9SSU5HEAMSIQodU1RBVFVTX1JFUE9SVF9TVEFUVVNfUkVTT0xWRUQQBCrlAQoTUGFnZUNvbXBvbmVudEltcGFjdBIlCiFQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfVU5TUEVDSUZJRUQQABIlCiFQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfT1BFUkFUSU9OQUwQARIuCipQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfREVHUkFERURfUEVSRk9STUFOQ0UQAhIoCiRQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfUEFSVElBTF9PVVRBR0UQAxImCiJQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfTUFKT1JfT1VUQUdFEARCXlpcZ2l0aHViLmNvbS9vcGVuc3RhdHVzaHEvb3BlbnN0YXR1cy9wYWNrYWdlcy9wcm90by9vcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjE7c3RhdHVzcmVwb3J0djFiBnByb3RvMw"); + fileDesc("Ci9vcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjEvc3RhdHVzX3JlcG9ydC5wcm90bxIbb3BlbnN0YXR1cy5zdGF0dXNfcmVwb3J0LnYxIm4KD0NvbXBvbmVudEltcGFjdBIZChFwYWdlX2NvbXBvbmVudF9pZBgBIAEoCRJACgZpbXBhY3QYAiABKA4yMC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuUGFnZUNvbXBvbmVudEltcGFjdCLdAQoSU3RhdHVzUmVwb3J0VXBkYXRlEgoKAmlkGAEgASgJEj8KBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXMSDAoEZGF0ZRgDIAEoCRIPCgdtZXNzYWdlGAQgASgJEhIKCmNyZWF0ZWRfYXQYBSABKAkSRwoRY29tcG9uZW50X2ltcGFjdHMYBiADKAsyLC5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuQ29tcG9uZW50SW1wYWN0It8BChNTdGF0dXNSZXBvcnRTdW1tYXJ5EgoKAmlkGAEgASgJEj8KBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXMSDQoFdGl0bGUYAyABKAkSGgoScGFnZV9jb21wb25lbnRfaWRzGAQgAygJEhIKCmNyZWF0ZWRfYXQYBSABKAkSEgoKdXBkYXRlZF9hdBgGIAEoCRIYCgtpbmNpZGVudF9pZBgHIAEoCUgAiAEBQg4KDF9pbmNpZGVudF9pZCKaAgoMU3RhdHVzUmVwb3J0EgoKAmlkGAEgASgJEj8KBnN0YXR1cxgCIAEoDjIvLm9wZW5zdGF0dXMuc3RhdHVzX3JlcG9ydC52MS5TdGF0dXNSZXBvcnRTdGF0dXMSDQoFdGl0bGUYAyABKAkSGgoScGFnZV9jb21wb25lbnRfaWRzGAQgAygJEkAKB3VwZGF0ZXMYBSADKAsyLy5vcGVuc3RhdHVzLnN0YXR1c19yZXBvcnQudjEuU3RhdHVzUmVwb3J0VXBkYXRlEhIKCmNyZWF0ZWRfYXQYBiABKAkSEgoKdXBkYXRlZF9hdBgHIAEoCRIYCgtpbmNpZGVudF9pZBgIIAEoCUgAiAEBQg4KDF9pbmNpZGVudF9pZCrPAQoSU3RhdHVzUmVwb3J0U3RhdHVzEiQKIFNUQVRVU19SRVBPUlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASJgoiU1RBVFVTX1JFUE9SVF9TVEFUVVNfSU5WRVNUSUdBVElORxABEiMKH1NUQVRVU19SRVBPUlRfU1RBVFVTX0lERU5USUZJRUQQAhIjCh9TVEFUVVNfUkVQT1JUX1NUQVRVU19NT05JVE9SSU5HEAMSIQodU1RBVFVTX1JFUE9SVF9TVEFUVVNfUkVTT0xWRUQQBCrlAQoTUGFnZUNvbXBvbmVudEltcGFjdBIlCiFQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfVU5TUEVDSUZJRUQQABIlCiFQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfT1BFUkFUSU9OQUwQARIuCipQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfREVHUkFERURfUEVSRk9STUFOQ0UQAhIoCiRQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfUEFSVElBTF9PVVRBR0UQAxImCiJQQUdFX0NPTVBPTkVOVF9JTVBBQ1RfTUFKT1JfT1VUQUdFEARCXlpcZ2l0aHViLmNvbS9vcGVuc3RhdHVzaHEvb3BlbnN0YXR1cy9wYWNrYWdlcy9wcm90by9vcGVuc3RhdHVzL3N0YXR1c19yZXBvcnQvdjE7c3RhdHVzcmVwb3J0djFiBnByb3RvMw"); /** * ComponentImpact pairs a page component with the impact an update set for it. @@ -143,6 +143,13 @@ export type StatusReportSummary = Message<"openstatus.status_report.v1.StatusRep * @generated from field: string updated_at = 6; */ updatedAt: string; + + /** + * ID of the incident this report communicates (unset when not linked). + * + * @generated from field: optional string incident_id = 7; + */ + incidentId?: string | undefined; }; /** @@ -206,6 +213,13 @@ export type StatusReport = Message<"openstatus.status_report.v1.StatusReport"> & * @generated from field: string updated_at = 7; */ updatedAt: string; + + /** + * ID of the incident this report communicates (unset when not linked). + * + * @generated from field: optional string incident_id = 8; + */ + incidentId?: string | undefined; }; /** diff --git a/packages/proto/package.json b/packages/proto/package.json index 12ce39dd..1a0b1b4e 100644 --- a/packages/proto/package.json +++ b/packages/proto/package.json @@ -36,6 +36,10 @@ "./private_location/v1": { "import": "./gen/ts/openstatus/private_location/v1/index.ts", "types": "./gen/ts/openstatus/private_location/v1/index.ts" + }, + "./incident/v1": { + "import": "./gen/ts/openstatus/incident/v1/index.ts", + "types": "./gen/ts/openstatus/incident/v1/index.ts" } }, "scripts": { diff --git a/packages/services/src/agent-tools/incident.ts b/packages/services/src/agent-tools/incident.ts index 0370361e..0f6e2d74 100644 --- a/packages/services/src/agent-tools/incident.ts +++ b/packages/services/src/agent-tools/incident.ts @@ -63,7 +63,7 @@ export const listIncidentsTool: AgentTool< inputSchema: ListIncidentsInput, outputSchema: ListIncidentsOutput, async run({ ctx, input }) { - const rows = await listIncidents({ + const { items: rows } = await listIncidents({ ctx, input: { status: input.status, limit: input.limit }, }); diff --git a/packages/services/src/incident/__tests__/effects.test.ts b/packages/services/src/incident/__tests__/effects.test.ts new file mode 100644 index 00000000..ff31b6d4 --- /dev/null +++ b/packages/services/src/incident/__tests__/effects.test.ts @@ -0,0 +1,463 @@ +import { db, eq } from "@openstatus/db"; +import { incident, integration, user } from "@openstatus/db/src/schema"; +import { + addUserToWorkspace, + createUser, +} from "@openstatus/db/src/test/factories"; +import { expect } from "@std/expect"; +import { beforeAll, describe, test } from "@std/testing/bdd"; + +import { + createWorkspaceFixture, + makeUserCtx, + withTestTransaction, +} from "../../../test/helpers"; +import type { DB, ServiceContext } from "../../context"; +import { SLACK_BOT_SCOPES } from "../../integration/slack-scopes"; +import type { Workspace } from "../../types"; +import { + afterIncidentClosed, + afterIncidentDeclared, + afterIncidentDeleted, + afterIncidentStatusChanged, + afterIncidentUpdated, + afterPostmortemApproved, + type CommanderEmail, + declareIncident, + describeIncidentChanges, + displayName, + type IncidentEffects, + notifyIncidentCommander, + resolveDashboardUrl, + type SlackIncidentClient, + updateIncident, +} from "../index"; + +type Call = { method: string; args: Record }; + +function fakeSlack() { + const calls: Call[] = []; + const record = (method: string, args: Call["args"]) => + calls.push({ method, args }); + const client: SlackIncidentClient = { + conversations: { + create: async (args) => { + record("create", args); + return { ok: true, channel: { id: "C_NEW", name: args.name } }; + }, + invite: async (args) => { + record("invite", args); + return { ok: true }; + }, + setTopic: async (args) => { + record("setTopic", args); + return { ok: true }; + }, + archive: async (args) => { + record("archive", args); + return { ok: true }; + }, + }, + chat: { + postMessage: async (args) => { + record("postMessage", args); + return { ok: true, ts: "1.1" }; + }, + }, + pins: { + add: async (args) => { + record("pins.add", args); + return { ok: true }; + }, + }, + users: { + lookupByEmail: async (args) => { + record("lookupByEmail", args); + throw new Error("users_not_found"); + }, + }, + }; + return { client, calls }; +} + +function makeEffects(opts: { failEmail?: boolean } = {}) { + const slack = fakeSlack(); + const emails: CommanderEmail[] = []; + const effects: IncidentEffects = { + clientFor: () => slack.client, + dashboardUrl: "https://dash.test", + sendCommanderEmail: async (email) => { + if (opts.failEmail) throw new Error("resend down"); + emails.push(email); + }, + actorLabel: "Jane", + assignedBy: "Jane Doe", + }; + return { effects, emails, calls: slack.calls }; +} + +let workspace: Workspace; +let ownerId: number; +let memberId: number; +let memberEmail: string; + +beforeAll(async () => { + const fixture = await createWorkspaceFixture("team"); + workspace = fixture.workspace; + ownerId = fixture.userId; + const member = await createUser(); + memberId = member.id; + memberEmail = member.email as string; + await addUserToWorkspace(member.id, workspace.id, "member"); +}); + +const ctxFor = (tx: DB, userId = ownerId): ServiceContext => ({ + ...makeUserCtx(workspace, { userId }), + db: tx, +}); + +async function connectSlack(tx: DB) { + await tx.insert(integration).values({ + name: "slack-agent", + workspaceId: workspace.id, + externalId: "T1", + credential: { botToken: "xoxb-test", botUserId: "UBOT" }, + data: { teamId: "T1", scopes: SLACK_BOT_SCOPES.join(",") }, + }); +} + +async function bind(tx: DB, incidentId: number) { + await tx + .update(incident) + .set({ slackTeamId: "T1", slackChannelId: "C1" }) + .where(eq(incident.id, incidentId)); +} + +function declare(tx: DB, commanderId: number | null = null) { + return declareIncident({ + ctx: ctxFor(tx), + input: { title: "API down", severity: "major", commanderId }, + }); +} + +describe("resolveDashboardUrl", () => { + test("override wins and loses its trailing slash", () => { + expect(resolveDashboardUrl({ override: "https://x.test/" })).toBe( + "https://x.test", + ); + }); + + test("falls back by environment", () => { + expect(resolveDashboardUrl({ nodeEnv: "production" })).toBe( + "https://app.openstatus.dev", + ); + expect(resolveDashboardUrl({ nodeEnv: "test", override: "" })).toBe( + "http://localhost:3001", + ); + }); +}); + +describe("notifyIncidentCommander", () => { + test("emails a new commander", async () => { + await withTestTransaction(async (tx) => { + const row = await declare(tx, memberId); + const { effects, emails } = makeEffects(); + await notifyIncidentCommander({ + ctx: ctxFor(tx), + effects, + incidentId: row.id, + }); + expect(emails).toHaveLength(1); + expect(emails[0]).toMatchObject({ + to: memberEmail, + incidentTitle: "API down", + severity: "major", + assignedBy: "Jane Doe", + url: `https://dash.test/incidents/${row.id}`, + }); + expect(emails[0]?.idempotencyKey).toBe( + `incident-commander:${row.id}:${memberId}:${row.updatedAt.getTime()}`, + ); + }); + }); + + test("skips the actor themselves", async () => { + await withTestTransaction(async (tx) => { + const row = await declare(tx, ownerId); + const { effects, emails } = makeEffects(); + await notifyIncidentCommander({ + ctx: ctxFor(tx), + effects, + incidentId: row.id, + }); + expect(emails).toHaveLength(0); + }); + }); + + test("skips an incident without a commander", async () => { + await withTestTransaction(async (tx) => { + const row = await declare(tx); + const { effects, emails } = makeEffects(); + await notifyIncidentCommander({ + ctx: ctxFor(tx), + effects, + incidentId: row.id, + }); + expect(emails).toHaveLength(0); + }); + }); + + test("a failing sender does not throw", async () => { + await withTestTransaction(async (tx) => { + const row = await declare(tx, memberId); + const { effects } = makeEffects({ failEmail: true }); + await notifyIncidentCommander({ + ctx: ctxFor(tx), + effects, + incidentId: row.id, + }); + }); + }); +}); + +describe("afterIncidentDeclared", () => { + test("no channel unless asked", async () => { + await withTestTransaction(async (tx) => { + await connectSlack(tx); + const row = await declare(tx); + const { effects, calls, emails } = makeEffects(); + const result = await afterIncidentDeclared({ + ctx: ctxFor(tx), + effects, + incident: row, + openSlackChannel: false, + }); + expect(result).toBeUndefined(); + expect(calls).toHaveLength(0); + expect(emails).toHaveLength(0); + }); + }); + + test("opens the channel and emails the commander when asked", async () => { + await withTestTransaction(async (tx) => { + await connectSlack(tx); + const row = await declare(tx, memberId); + const { effects, calls, emails } = makeEffects(); + const result = await afterIncidentDeclared({ + ctx: ctxFor(tx), + effects, + incident: row, + openSlackChannel: true, + }); + expect(result?.status).toBe("bound"); + expect(calls.some((c) => c.method === "create")).toBe(true); + expect(emails).toHaveLength(1); + }); + }); + + test("skips the channel when Slack is not connected", async () => { + await withTestTransaction(async (tx) => { + const row = await declare(tx); + const { effects, calls } = makeEffects(); + const result = await afterIncidentDeclared({ + ctx: ctxFor(tx), + effects, + incident: row, + openSlackChannel: true, + }); + expect(result).toEqual({ status: "skipped" }); + expect(calls).toHaveLength(0); + }); + }); +}); + +describe("describeIncidentChanges", () => { + const base = { + title: "API down", + summary: null, + startedAt: new Date("2026-10-01T10:00:00Z"), + severity: "major" as const, + commanderId: null, + }; + + test("lists every changed field", () => { + const changes = describeIncidentChanges( + base, + { + title: "API down", + summary: "s", + startedAt: new Date("2026-10-01T09:00:00Z"), + severity: "critical", + commanderId: 7, + }, + "Ann", + ); + expect(changes).toEqual([ + "title is now *API <really> down*", + "summary was updated", + `start time is now `, + "severity is now *critical*", + "Ann is now commander", + ]); + }); + + test("reports removals and nothing for no change", () => { + expect(describeIncidentChanges(base, base, null)).toEqual([]); + expect( + describeIncidentChanges( + { ...base, summary: "s", commanderId: 7 }, + base, + null, + ), + ).toEqual(["summary was removed", "there is no commander"]); + }); +}); + +describe("channel announcements", () => { + test("nothing is posted for an unbound incident", async () => { + await withTestTransaction(async (tx) => { + await connectSlack(tx); + const row = await declare(tx); + const { effects, calls } = makeEffects(); + await afterIncidentStatusChanged({ + ctx: ctxFor(tx), + effects, + incidentId: row.id, + status: "canceled", + }); + expect(calls).toHaveLength(0); + }); + }); + + test("a status change posts and refreshes the topic", async () => { + await withTestTransaction(async (tx) => { + await connectSlack(tx); + const row = await declare(tx); + await bind(tx, row.id); + const { effects, calls } = makeEffects(); + await afterIncidentStatusChanged({ + ctx: ctxFor(tx), + effects, + incidentId: row.id, + status: "mitigated", + note: "rolled back", + }); + expect(calls.map((c) => c.method)).toEqual(["postMessage", "setTopic"]); + expect(calls[0]?.args.text).toBe( + "Jane marked the incident *mitigated*.\n>rolled back", + ); + }); + }); + + test("cancel, close, approve-with-close and delete archive", async () => { + await withTestTransaction(async (tx) => { + await connectSlack(tx); + const row = await declare(tx); + await bind(tx, row.id); + const ctx = ctxFor(tx); + const archives = async (run: (e: IncidentEffects) => Promise) => { + const { effects, calls } = makeEffects(); + await run(effects); + return calls.some((c) => c.method === "archive"); + }; + expect( + await archives((effects) => + afterIncidentStatusChanged({ + ctx, + effects, + incidentId: row.id, + status: "canceled", + }), + ), + ).toBe(true); + expect( + await archives((effects) => + afterIncidentClosed({ ctx, effects, incidentId: row.id }), + ), + ).toBe(true); + expect( + await archives((effects) => + afterPostmortemApproved({ + ctx, + effects, + incidentId: row.id, + closed: true, + }), + ), + ).toBe(true); + expect( + await archives((effects) => + afterPostmortemApproved({ + ctx, + effects, + incidentId: row.id, + closed: false, + }), + ), + ).toBe(false); + expect( + await archives((effects) => + afterIncidentDeleted({ + ctx, + effects, + before: { ...row, slackTeamId: "T1", slackChannelId: "C1" }, + }), + ), + ).toBe(true); + }); + }); + + test("a channel from another Slack team is left alone", async () => { + await withTestTransaction(async (tx) => { + await connectSlack(tx); + const row = await declare(tx); + const { effects, calls } = makeEffects(); + await afterIncidentDeleted({ + ctx: ctxFor(tx), + effects, + before: { ...row, slackTeamId: "T_OTHER", slackChannelId: "C1" }, + }); + expect(calls).toHaveLength(0); + }); + }); + + test("an update announces its changes and emails the new commander", async () => { + await withTestTransaction(async (tx) => { + await connectSlack(tx); + const before = await declare(tx); + await bind(tx, before.id); + const after = await updateIncident({ + ctx: ctxFor(tx), + input: { id: before.id, severity: "critical", commanderId: memberId }, + }); + const { effects, calls, emails } = makeEffects(); + await afterIncidentUpdated({ ctx: ctxFor(tx), effects, before, after }); + expect(emails).toHaveLength(1); + const member = await tx + .select({ + name: user.name, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + }) + .from(user) + .where(eq(user.id, memberId)) + .get(); + expect(member).toBeDefined(); + expect(calls[0]?.args.text).toBe( + `Jane: severity is now *critical*, ${member ? displayName(member) : ""} is now commander.`, + ); + }); + }); +}); + +describe("cleanup", () => { + test("leaves no integration behind", async () => { + const rows = await db + .select({ id: integration.id }) + .from(integration) + .where(eq(integration.workspaceId, workspace.id)) + .all(); + expect(rows).toHaveLength(0); + }); +}); diff --git a/packages/services/src/incident/__tests__/incident.test.ts b/packages/services/src/incident/__tests__/incident.test.ts index 6b2384d7..23f1ebef 100644 --- a/packages/services/src/incident/__tests__/incident.test.ts +++ b/packages/services/src/incident/__tests__/incident.test.ts @@ -42,12 +42,14 @@ import { declareIncident, deleteIncident, getIncident, + getIncidentOrThrow, linkIncidentStatusReport, listIncidentEvents, listIncidents, setIncidentStatus, unbindIncidentSlackChannel, unlinkIncidentStatusReport, + toIncidentView, updateIncident, } from "../index"; @@ -752,9 +754,9 @@ describe("reads", () => { tx, ); const ctx = as(memberId, tx); - const ids = (await listIncidents({ ctx })).map((i) => i.id); + const ids = (await listIncidents({ ctx })).items.map((i) => i.id); expect(ids.indexOf(open.id)).toBeLessThan(ids.indexOf(resolved.id)); - const onlyResolved = await listIncidents({ + const { items: onlyResolved } = await listIncidents({ ctx, input: { status: ["resolved"] }, }); @@ -762,6 +764,137 @@ describe("reads", () => { }); }); + test("list filters by closed", async () => { + await withTestTransaction(async (tx) => { + const closed = await createIncident( + workspace.id, + { status: "resolved", resolvedAt: new Date(), closedAt: new Date() }, + tx, + ); + const resolved = await createIncident( + workspace.id, + { status: "resolved", resolvedAt: new Date() }, + tx, + ); + const ctx = as(memberId, tx); + const onlyClosed = ( + await listIncidents({ ctx, input: { closed: true } }) + ).items.map((i) => i.id); + expect(onlyClosed).toContain(closed.id); + expect(onlyClosed).not.toContain(resolved.id); + const notClosed = ( + await listIncidents({ + ctx, + input: { status: ["resolved"], closed: false }, + }) + ).items.map((i) => i.id); + expect(notClosed).toContain(resolved.id); + expect(notClosed).not.toContain(closed.id); + }); + }); + + test("list returns the total size across pages", async () => { + await withTestTransaction(async (tx) => { + for (let i = 0; i < 3; i++) { + await createIncident(workspace.id, { status: "open" }, tx); + } + const ctx = as(memberId, tx); + const all = await listIncidents({ ctx, input: { limit: 100 } }); + const total = all.items.length; + expect(all.totalSize).toBe(total); + const first = await listIncidents({ ctx, input: { limit: 2 } }); + expect(first.items).toHaveLength(2); + expect(first.totalSize).toBe(total); + const last = await listIncidents({ + ctx, + input: { limit: 2, offset: total - 1 }, + }); + expect(last.items).toHaveLength(1); + expect(last.totalSize).toBe(total); + const past = await listIncidents({ + ctx, + input: { limit: 2, offset: total + 5 }, + }); + expect(past.items).toHaveLength(0); + expect(past.totalSize).toBe(total); + }); + }); + + test("get carries allowed transitions and deletable", async () => { + await withTestTransaction(async (tx) => { + const row = await declare(tx); + const ctx = as(memberId, tx); + const open = await getIncidentOrThrow({ ctx, input: { id: row.id } }); + expect(open.allowedTransitions).toEqual([ + "mitigated", + "resolved", + "canceled", + ]); + expect(open.deletable).toBe(true); + await setIncidentStatus({ + ctx, + input: { id: row.id, status: "mitigated" }, + }); + const mitigated = await getIncidentOrThrow({ + ctx, + input: { id: row.id }, + }); + expect(mitigated.allowedTransitions).toEqual([ + "resolved", + "open", + "canceled", + ]); + expect(mitigated.deletable).toBe(false); + }); + }); + + test("the view of a closed or canceled incident has no transitions", () => { + const base = { + id: 1, + workspaceId: 1, + title: "x", + severity: "minor" as const, + summary: null, + commanderId: null, + declaredBy: null, + declaredAt: new Date(), + startedAt: new Date(), + mitigatedAt: null, + resolvedAt: new Date(), + resolvedBy: null, + closedAt: new Date(), + statusReportId: null, + slackTeamId: null, + slackChannelId: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + const closed = toIncidentView({ ...base, status: "resolved" }); + expect(closed.allowedTransitions).toEqual([]); + expect(closed.deletable).toBe(false); + const canceled = toIncidentView({ ...base, status: "canceled" }); + expect(canceled.allowedTransitions).toEqual([]); + expect(canceled.deletable).toBe(false); + }); + + test("getIncidentOrThrow is NotFound for another workspace", async () => { + await withTestTransaction(async (tx) => { + const theirs = await createIncident(otherWorkspace.id, {}, tx); + await expect( + getIncidentOrThrow({ + ctx: as(memberId, tx), + input: { id: theirs.id }, + }), + ).rejects.toThrow(NotFoundError); + await expect( + getIncidentOrThrow({ + ctx: as(memberId, tx), + input: { id: 999_999_999 }, + }), + ).rejects.toThrow(NotFoundError); + }); + }); + test("another workspace's incident is not visible", async () => { await withTestTransaction(async (tx) => { const theirs = await createIncident(otherWorkspace.id, {}, tx); diff --git a/packages/services/src/incident/effects.ts b/packages/services/src/incident/effects.ts new file mode 100644 index 00000000..474a8b42 --- /dev/null +++ b/packages/services/src/incident/effects.ts @@ -0,0 +1,238 @@ +import type { + Incident, + IncidentSeverity, + IncidentStatus, +} from "@openstatus/db/src/schema"; + +import { displayName } from "../attribution"; +import { type ServiceContext, getReadDb, tryGetActorUserId } from "../context"; +import { userDisplayName } from "./internal"; +import { getIncident } from "./list"; +import { + type OpenChannelResult, + type SlackClientFactory, + announceInChannel, + announceIncidentChange, + escapeMrkdwn, + openIncidentSlackChannel, +} from "./slack-flow"; + +export type CommanderEmail = { + to: string; + incidentTitle: string; + severity: IncidentSeverity; + workspaceName: string; + assignedBy: string; + url: string; + idempotencyKey: string; +}; + +/** What an adapter supplies so every surface runs the same follow-ups. */ +export type IncidentEffects = { + clientFor: SlackClientFactory; + dashboardUrl: string; + sendCommanderEmail: (email: CommanderEmail) => Promise; + /** Slack mrkdwn naming the actor in channel messages. */ + actorLabel: string; + /** Plain name shown as "assigned by" in the commander email. */ + assignedBy: string; +}; + +type EffectArgs = { ctx: ServiceContext; effects: IncidentEffects }; + +export function resolveDashboardUrl(env: { + nodeEnv?: string; + override?: string; +}): string { + if (env.override) return env.override.replace(/\/+$/, ""); + return env.nodeEnv === "production" + ? "https://app.openstatus.dev" + : "http://localhost:3001"; +} + +export function incidentDashboardUrl( + dashboardUrl: string, + incidentId: number, +): string { + return `${dashboardUrl}/incidents/${incidentId}`; +} + +export async function actorDisplayName( + ctx: ServiceContext, +): Promise { + return userDisplayName(getReadDb(ctx), tryGetActorUserId(ctx.actor)); +} + +export function quoteNote(note: string | undefined): string { + return note ? `\n>${escapeMrkdwn(note).replaceAll("\n", "\n>")}` : ""; +} + +/** Best effort: a failed email never fails the mutation that assigned them. */ +export async function notifyIncidentCommander( + args: EffectArgs & { incidentId: number }, +): Promise { + const { ctx, effects, incidentId } = args; + try { + const row = await getIncident({ ctx, input: { id: incidentId } }); + const commander = row?.commander; + if (!row || !commander?.email) return; + if (commander.id === tryGetActorUserId(ctx.actor)) return; + await effects.sendCommanderEmail({ + to: commander.email, + incidentTitle: row.title, + severity: row.severity, + workspaceName: ctx.workspace.name ?? ctx.workspace.slug, + assignedBy: effects.assignedBy, + url: incidentDashboardUrl(effects.dashboardUrl, row.id), + idempotencyKey: `incident-commander:${row.id}:${commander.id}:${row.updatedAt.getTime()}`, + }); + } catch (err) { + console.warn("incident commander email failed", { incidentId, err }); + } +} + +export async function afterIncidentDeclared( + args: EffectArgs & { + incident: Pick; + openSlackChannel: boolean; + }, +): Promise { + const { ctx, effects, incident } = args; + if (incident.commanderId !== null) { + await notifyIncidentCommander({ ctx, effects, incidentId: incident.id }); + } + if (!args.openSlackChannel) return undefined; + return openIncidentSlackChannel({ + ctx, + incidentId: incident.id, + clientFor: effects.clientFor, + dashboardUrl: effects.dashboardUrl, + }); +} + +type ChangeFields = Pick< + Incident, + "title" | "summary" | "startedAt" | "severity" | "commanderId" +>; + +export function describeIncidentChanges( + before: ChangeFields, + after: ChangeFields, + commanderName: string | null, +): string[] { + const changes: string[] = []; + if (after.title !== before.title) { + changes.push(`title is now *${escapeMrkdwn(after.title)}*`); + } + if (after.summary !== before.summary) { + changes.push(after.summary ? "summary was updated" : "summary was removed"); + } + if (after.startedAt.getTime() !== before.startedAt.getTime()) { + const seconds = Math.floor(after.startedAt.getTime() / 1000); + changes.push( + `start time is now `, + ); + } + if (after.severity !== before.severity) { + changes.push(`severity is now *${after.severity}*`); + } + if (after.commanderId !== before.commanderId) { + changes.push( + commanderName + ? `${escapeMrkdwn(commanderName)} is now commander` + : "there is no commander", + ); + } + return changes; +} + +export async function afterIncidentUpdated( + args: EffectArgs & { before: ChangeFields; after: Incident }, +): Promise { + const { ctx, effects, before, after } = args; + const commanderChanged = after.commanderId !== before.commanderId; + let commanderName: string | null = null; + if (commanderChanged && after.commanderId !== null) { + await notifyIncidentCommander({ ctx, effects, incidentId: after.id }); + const fresh = await getIncident({ ctx, input: { id: after.id } }); + commanderName = fresh?.commander ? displayName(fresh.commander) : null; + } + const changes = describeIncidentChanges(before, after, commanderName); + if (!changes.length) return; + await announceIncidentChange({ + ctx, + incidentId: after.id, + text: `${effects.actorLabel}: ${changes.join(", ")}.`, + clientFor: effects.clientFor, + dashboardUrl: effects.dashboardUrl, + }); +} + +export async function afterIncidentStatusChanged( + args: EffectArgs & { + incidentId: number; + status: IncidentStatus; + note?: string; + archive?: boolean; + }, +): Promise { + const { ctx, effects, status } = args; + await announceIncidentChange({ + ctx, + incidentId: args.incidentId, + text: `${effects.actorLabel} marked the incident *${status}*.${quoteNote(args.note)}`, + clientFor: effects.clientFor, + dashboardUrl: effects.dashboardUrl, + archive: args.archive ?? status === "canceled", + }); +} + +export async function afterIncidentClosed( + args: EffectArgs & { incidentId: number }, +): Promise { + const { ctx, effects } = args; + await announceIncidentChange({ + ctx, + incidentId: args.incidentId, + text: `${effects.actorLabel} closed the incident.`, + clientFor: effects.clientFor, + dashboardUrl: effects.dashboardUrl, + archive: true, + }); +} + +export async function afterPostmortemApproved( + args: EffectArgs & { incidentId: number; closed: boolean }, +): Promise { + const { ctx, effects, closed } = args; + await announceIncidentChange({ + ctx, + incidentId: args.incidentId, + text: closed + ? `${effects.actorLabel} approved the postmortem and closed the incident.` + : `${effects.actorLabel} approved the postmortem.`, + clientFor: effects.clientFor, + dashboardUrl: effects.dashboardUrl, + archive: closed, + }); +} + +export async function afterIncidentDeleted( + args: EffectArgs & { + before: Pick< + Incident, + "id" | "severity" | "status" | "slackChannelId" | "slackTeamId" + >; + }, +): Promise { + const { ctx, effects, before } = args; + if (!before.slackChannelId) return; + await announceInChannel({ + ctx, + incident: before, + text: `${effects.actorLabel} deleted this incident: it was declared by mistake.`, + clientFor: effects.clientFor, + dashboardUrl: effects.dashboardUrl, + archive: true, + }); +} diff --git a/packages/services/src/incident/index.ts b/packages/services/src/incident/index.ts index b4f6ddfb..e85e4be7 100644 --- a/packages/services/src/incident/index.ts +++ b/packages/services/src/incident/index.ts @@ -2,6 +2,22 @@ export { addIncidentNote } from "./add-note"; export { closeIncident, closeIncidentInTx } from "./close"; export { declareIncident } from "./declare"; export { deleteIncident, isDeletable } from "./delete"; +export { + actorDisplayName, + afterIncidentClosed, + afterIncidentDeclared, + afterIncidentDeleted, + afterIncidentStatusChanged, + afterIncidentUpdated, + afterPostmortemApproved, + type CommanderEmail, + describeIncidentChanges, + incidentDashboardUrl, + type IncidentEffects, + notifyIncidentCommander, + quoteNote, + resolveDashboardUrl, +} from "./effects"; export { displayName } from "../attribution"; export { allowedTransitions } from "./internal"; export { @@ -22,7 +38,9 @@ export { getIncident, getIncidentBySlackChannel, getIncidentForStatusReport, + getIncidentOrThrow, listIncidents, + toIncidentView, } from "./list"; export { approvePostmortem, diff --git a/packages/services/src/incident/internal.ts b/packages/services/src/incident/internal.ts index 294f53c2..4919416b 100644 --- a/packages/services/src/incident/internal.ts +++ b/packages/services/src/incident/internal.ts @@ -18,6 +18,16 @@ import { getMembership } from "../member/membership"; export const INCIDENT_FEATURE = "incident-management"; +export const incidentUserColumns = { + id: true, + name: true, + firstName: true, + lastName: true, + email: true, + photoUrl: true, + deletedAt: true, +} as const; + export function requireIncidentFeature(ctx: ServiceContext): void { requireFeature(ctx, INCIDENT_FEATURE); } diff --git a/packages/services/src/incident/list-events.ts b/packages/services/src/incident/list-events.ts index 97f4a21b..35dfabcf 100644 --- a/packages/services/src/incident/list-events.ts +++ b/packages/services/src/incident/list-events.ts @@ -2,7 +2,11 @@ import { desc, eq } from "@openstatus/db"; import { incidentEvent } from "@openstatus/db/src/schema"; import { type ServiceContext, getReadDb } from "../context"; -import { getIncidentInWorkspace, requireIncidentFeature } from "./internal"; +import { + getIncidentInWorkspace, + incidentUserColumns, + requireIncidentFeature, +} from "./internal"; import { ListIncidentEventsInput } from "./schemas"; /** The incident's timeline, newest first. */ @@ -20,17 +24,7 @@ export async function listIncidentEvents(args: { orderBy: [desc(incidentEvent.createdAt), desc(incidentEvent.id)], limit: input.limit, with: { - createdByUser: { - columns: { - id: true, - name: true, - firstName: true, - lastName: true, - email: true, - photoUrl: true, - deletedAt: true, - }, - }, + createdByUser: { columns: incidentUserColumns }, }, }); } diff --git a/packages/services/src/incident/list.ts b/packages/services/src/incident/list.ts index 1160038e..91593119 100644 --- a/packages/services/src/incident/list.ts +++ b/packages/services/src/incident/list.ts @@ -1,23 +1,37 @@ -import { and, desc, eq, inArray, sql } from "@openstatus/db"; -import { incident } from "@openstatus/db/src/schema"; +import { and, desc, eq, inArray, isNotNull, isNull, sql } from "@openstatus/db"; +import { + type Incident, + type IncidentStatus, + incident, +} from "@openstatus/db/src/schema"; import { type ServiceContext, getReadDb } from "../context"; +import { NotFoundError } from "../errors"; import { isFeatureEnabled } from "../features"; -import { INCIDENT_FEATURE, requireIncidentFeature } from "./internal"; +import { isDeletable } from "./delete"; +import { + INCIDENT_FEATURE, + allowedTransitions, + incidentUserColumns, + requireIncidentFeature, +} from "./internal"; import { IncidentIdInput, ListIncidentsInput } from "./schemas"; -const userColumns = { - id: true, - name: true, - firstName: true, - lastName: true, - email: true, - photoUrl: true, - deletedAt: true, -} as const; - const statusOrder = sql`case ${incident.status} when 'open' then 0 when 'mitigated' then 1 when 'resolved' then 2 else 3 end`; +export function toIncidentView( + row: T, +): T & { + allowedTransitions: ReadonlyArray; + deletable: boolean; +} { + return { + ...row, + allowedTransitions: allowedTransitions(row), + deletable: isDeletable(row), + }; +} + /** Open incidents first, then newest declared. */ export async function listIncidents(args: { ctx: ServiceContext; @@ -26,21 +40,42 @@ export async function listIncidents(args: { const { ctx } = args; requireIncidentFeature(ctx); const input = ListIncidentsInput.parse(args.input ?? {}); + const db = getReadDb(ctx); const where = and( eq(incident.workspaceId, ctx.workspace.id), input.status?.length ? inArray(incident.status, input.status) : undefined, + input.closed === undefined + ? undefined + : input.closed + ? isNotNull(incident.closedAt) + : isNull(incident.closedAt), ); - return getReadDb(ctx).query.incident.findMany({ + const items = await db.query.incident.findMany({ where, orderBy: [statusOrder, desc(incident.declaredAt), desc(incident.id)], limit: input.limit, offset: input.offset, with: { - commander: { columns: userColumns }, + commander: { columns: incidentUserColumns }, statusReport: { columns: { id: true, title: true, status: true } }, }, }); + + // A short page is the last page, so the count is only needed for a full one. + let totalSize = input.offset + items.length; + if ( + items.length === input.limit || + (items.length === 0 && input.offset > 0) + ) { + const row = await db + .select({ count: sql`count(*)` }) + .from(incident) + .where(where) + .get(); + totalSize = row?.count ?? totalSize; + } + return { items, totalSize }; } export async function getIncident(args: { @@ -50,20 +85,30 @@ export async function getIncident(args: { const { ctx } = args; requireIncidentFeature(ctx); const input = IncidentIdInput.parse(args.input); - return getReadDb(ctx).query.incident.findFirst({ + const row = await getReadDb(ctx).query.incident.findFirst({ where: and( eq(incident.id, input.id), eq(incident.workspaceId, ctx.workspace.id), ), with: { - commander: { columns: userColumns }, - declaredByUser: { columns: userColumns }, - resolvedByUser: { columns: userColumns }, + commander: { columns: incidentUserColumns }, + declaredByUser: { columns: incidentUserColumns }, + resolvedByUser: { columns: incidentUserColumns }, statusReport: { columns: { id: true, title: true, status: true, pageId: true }, }, }, }); + return row ? toIncidentView(row) : undefined; +} + +export async function getIncidentOrThrow(args: { + ctx: ServiceContext; + input: IncidentIdInput; +}) { + const row = await getIncident(args); + if (!row) throw new NotFoundError("incident", args.input.id); + return row; } /** The incident a status report communicates, if any. */ @@ -99,7 +144,7 @@ export async function getIncidentBySlackChannel(args: { eq(incident.slackChannelId, args.input.channelId), ), with: { - commander: { columns: userColumns }, + commander: { columns: incidentUserColumns }, statusReport: { columns: { id: true, title: true, status: true } }, }, }); diff --git a/packages/services/src/incident/postmortem.ts b/packages/services/src/incident/postmortem.ts index 6459b1e9..967aa555 100644 --- a/packages/services/src/incident/postmortem.ts +++ b/packages/services/src/incident/postmortem.ts @@ -19,6 +19,7 @@ import { closeIncidentInTx } from "./close"; import { appendIncidentEvent, getIncidentInWorkspace, + incidentUserColumns, requireIncidentFeature, } from "./internal"; import { @@ -202,11 +203,14 @@ export async function approvePostmortem(args: { export async function getPostmortem(args: { ctx: ServiceContext; input: IncidentIdInput; -}): Promise { +}) { const { ctx } = args; requireIncidentFeature(ctx); const input = IncidentIdInput.parse(args.input); const db = getReadDb(ctx); const row = await getIncidentInWorkspace(db, ctx.workspace.id, input.id); - return findPostmortem(db, row.id); + return db.query.incidentPostmortem.findFirst({ + where: eq(incidentPostmortem.incidentId, row.id), + with: { approvedByUser: { columns: incidentUserColumns } }, + }); } diff --git a/packages/services/src/incident/schemas.ts b/packages/services/src/incident/schemas.ts index 0e9a4e28..f2a3bb9c 100644 --- a/packages/services/src/incident/schemas.ts +++ b/packages/services/src/incident/schemas.ts @@ -101,6 +101,7 @@ export type BindIncidentSlackChannelInput = z.infer< export const ListIncidentsInput = z.object({ status: z.array(z.enum(incidentStatus)).optional(), + closed: z.boolean().optional(), limit: z.number().int().min(1).max(100).default(50), offset: z.number().int().min(0).default(0), }); diff --git a/packages/services/src/status-report/__tests__/incident-id.test.ts b/packages/services/src/status-report/__tests__/incident-id.test.ts new file mode 100644 index 00000000..a80b58aa --- /dev/null +++ b/packages/services/src/status-report/__tests__/incident-id.test.ts @@ -0,0 +1,93 @@ +import { db } from "@openstatus/db"; +import { page } from "@openstatus/db/src/schema"; +import { createIncident } from "@openstatus/db/src/test/factories"; +import { expect } from "@std/expect"; +import { beforeAll, describe, test } from "@std/testing/bdd"; + +import { + createWorkspaceFixture, + makeUserCtx, + withTestTransaction, +} from "../../../test/helpers"; +import type { ServiceContext } from "../../context"; +import type { Workspace } from "../../types"; +import { createStatusReport } from "../create"; +import { getStatusReport, listStatusReports } from "../list"; + +const TEST_PREFIX = "svc-status-report-incident-id"; + +let workspace: Workspace; +let ctx: ServiceContext; +let pageId: number; + +beforeAll(async () => { + const fixture = await createWorkspaceFixture("team"); + workspace = fixture.workspace; + ctx = makeUserCtx(workspace, { userId: fixture.userId }); + const row = await db + .insert(page) + .values({ + workspaceId: workspace.id, + title: `${TEST_PREFIX}-page`, + description: "", + slug: `${TEST_PREFIX}-${workspace.id}`, + customDomain: "", + }) + .returning() + .get(); + pageId = row.id; +}); + +function create(c: ServiceContext, incidentId?: number) { + return createStatusReport({ + ctx: c, + input: { + title: `${TEST_PREFIX}-report`, + status: "investigating", + message: "m", + date: new Date(), + pageId, + pageComponentIds: [], + incidentId, + }, + }); +} + +describe("status report incidentId", () => { + test("is null when no incident links the report", async () => { + await withTestTransaction(async (tx) => { + const c = { ...ctx, db: tx }; + const { statusReport } = await create(c); + const full = await getStatusReport({ + ctx: c, + input: { id: statusReport.id }, + }); + expect(full.incidentId).toBeNull(); + }); + }); + + test("get and list return the linked incident", async () => { + await withTestTransaction(async (tx) => { + const c = { ...ctx, db: tx }; + const linked = await createIncident(workspace.id, {}, tx); + const { statusReport } = await create(c, linked.id); + const full = await getStatusReport({ + ctx: c, + input: { id: statusReport.id }, + }); + expect(full.incidentId).toBe(linked.id); + const { items } = await listStatusReports({ + ctx: c, + input: { + pageId, + limit: 100, + offset: 0, + statuses: [], + order: "desc", + }, + }); + const row = items.find((r) => r.id === statusReport.id); + expect(row?.incidentId).toBe(linked.id); + }); + }); +}); diff --git a/packages/services/src/status-report/list.ts b/packages/services/src/status-report/list.ts index a333eff5..fa9e6ff9 100644 --- a/packages/services/src/status-report/list.ts +++ b/packages/services/src/status-report/list.ts @@ -10,6 +10,7 @@ import { sql, } from "@openstatus/db"; import { + incident, page as pageTable, pageComponent, type PageComponentImpact, @@ -69,6 +70,7 @@ export type StatusReportWithRelations = StatusReport & pageComponents: PageComponent[]; /** Flat list of associated component ids. Convenience for proto conversion. */ pageComponentIds: number[]; + incidentId: number | null; /** * The owning page with its full component roster. `null` when the report * has no `pageId` — rare today but schema-allowed. @@ -105,30 +107,43 @@ async function enrichReportsBatch( // Everything that keys off the reports themselves — updates, component // associations, owning pages and their component rosters — in one // round-trip. Only the impact rows below depend on a prior result. - const [allUpdates, assocRows, pageRows, pageSiblings] = await batchReads(db, [ - db - .select() - .from(statusReportUpdate) - .where(inArray(statusReportUpdate.statusReportId, reportIds)) - .orderBy(desc(statusReportUpdate.date), desc(statusReportUpdate.id)), - // Explicit column selection with aliases avoids depending on drizzle's - // auto-derived `row.` keys — those are named after the - // exported JS variable, so a rename in the schema silently breaks the - // row shape at runtime. - db - .select({ - reportId: statusReportsToPageComponents.statusReportId, - component: pageComponent, - }) - .from(pageComponent) - .innerJoin( - statusReportsToPageComponents, - eq(statusReportsToPageComponents.pageComponentId, pageComponent.id), - ) - .where(inArray(statusReportsToPageComponents.statusReportId, reportIds)), - db.select().from(pageTable).where(anyPage), - db.select().from(pageComponent).where(anyPageComponent), - ]); + const [allUpdates, assocRows, pageRows, pageSiblings, incidentRows] = + await batchReads(db, [ + db + .select() + .from(statusReportUpdate) + .where(inArray(statusReportUpdate.statusReportId, reportIds)) + .orderBy(desc(statusReportUpdate.date), desc(statusReportUpdate.id)), + // Explicit column selection with aliases avoids depending on drizzle's + // auto-derived `row.` keys — those are named after the + // exported JS variable, so a rename in the schema silently breaks the + // row shape at runtime. + db + .select({ + reportId: statusReportsToPageComponents.statusReportId, + component: pageComponent, + }) + .from(pageComponent) + .innerJoin( + statusReportsToPageComponents, + eq(statusReportsToPageComponents.pageComponentId, pageComponent.id), + ) + .where( + inArray(statusReportsToPageComponents.statusReportId, reportIds), + ), + db.select().from(pageTable).where(anyPage), + db.select().from(pageComponent).where(anyPageComponent), + db + .select({ id: incident.id, statusReportId: incident.statusReportId }) + .from(incident) + .where(inArray(incident.statusReportId, reportIds)), + ]); + const incidentByReport = new Map(); + for (const row of incidentRows) { + if (row.statusReportId !== null) { + incidentByReport.set(row.statusReportId, row.id); + } + } // One query: all impact rows for all updates. const updateIds = allUpdates.map((u) => u.id); @@ -225,6 +240,7 @@ async function enrichReportsBatch( updates: updatesByReport.get(r.id) ?? [], pageComponents: components, pageComponentIds: components.map((c) => c.id), + incidentId: incidentByReport.get(r.id) ?? null, page: r.pageId != null ? (pageById.get(r.pageId) ?? null) : null, }; });