# OpenAPI 3.1 specification for StellaOps Notifier WebService (draft) openapi: 3.1.0 info: title: StellaOps Notifier API version: 0.6.0-draft description: | Contract for Notifications Studio (Notifier) covering rules, templates, incidents, and quiet hours. Uses the platform error envelope and tenant header `X-StellaOps-Tenant`. servers: - url: https://api.stellaops.example.com description: Production - url: https://api.dev.stellaops.example.com description: Development security: - oauth2: [notify.viewer] - oauth2: [notify.operator] - oauth2: [notify.admin] paths: /api/v1/notify/rules: get: summary: List notification rules tags: [Rules] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Paginated rule list content: application/json: schema: type: object properties: items: type: array items: { $ref: '#/components/schemas/NotifyRule' } nextPageToken: type: string examples: default: value: items: - ruleId: rule-critical tenantId: tenant-dev name: Critical scanner verdicts enabled: true match: eventKinds: [scanner.report.ready] minSeverity: critical actions: - actionId: act-slack-critical channel: chn-slack-soc template: tmpl-critical digest: instant nextPageToken: null default: $ref: '#/components/responses/Error' post: summary: Create a notification rule tags: [Rules] parameters: - $ref: '#/components/parameters/Tenant' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/NotifyRule' } examples: create-rule: value: ruleId: rule-attest-fail tenantId: tenant-dev name: Attestation failures → SOC enabled: true match: eventKinds: [attestor.verification.failed] actions: - actionId: act-soc channel: chn-webhook-soc template: tmpl-attest-verify-fail responses: '201': description: Rule created content: application/json: schema: { $ref: '#/components/schemas/NotifyRule' } default: $ref: '#/components/responses/Error' /api/v1/notify/rules/{ruleId}: get: summary: Fetch a rule tags: [Rules] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/RuleId' responses: '200': description: Rule content: application/json: schema: { $ref: '#/components/schemas/NotifyRule' } default: $ref: '#/components/responses/Error' patch: summary: Update a rule (partial) tags: [Rules] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/RuleId' requestBody: required: true content: application/json: schema: type: object description: JSON Merge Patch responses: '200': description: Updated rule content: application/json: schema: { $ref: '#/components/schemas/NotifyRule' } default: $ref: '#/components/responses/Error' /api/v1/notify/templates: get: summary: List templates tags: [Templates] parameters: - $ref: '#/components/parameters/Tenant' - name: key in: query description: Filter by template key schema: { type: string } responses: '200': description: Templates content: application/json: schema: type: array items: { $ref: '#/components/schemas/NotifyTemplate' } default: $ref: '#/components/responses/Error' post: summary: Create a template tags: [Templates] parameters: - $ref: '#/components/parameters/Tenant' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/NotifyTemplate' } responses: '201': description: Template created content: application/json: schema: { $ref: '#/components/schemas/NotifyTemplate' } default: $ref: '#/components/responses/Error' /api/v1/notify/templates/{templateId}: get: summary: Fetch a template tags: [Templates] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/TemplateId' responses: '200': description: Template content: application/json: schema: { $ref: '#/components/schemas/NotifyTemplate' } default: $ref: '#/components/responses/Error' patch: summary: Update a template (partial) tags: [Templates] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/TemplateId' requestBody: required: true content: application/json: schema: type: object description: JSON Merge Patch responses: '200': description: Updated template content: application/json: schema: { $ref: '#/components/schemas/NotifyTemplate' } default: $ref: '#/components/responses/Error' /api/v1/notify/incidents: get: summary: List incidents (paged) tags: [Incidents] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Incident page content: application/json: schema: type: object properties: items: type: array items: { $ref: '#/components/schemas/Incident' } nextPageToken: { type: string } default: $ref: '#/components/responses/Error' post: summary: Raise an incident (ops/toggle/override) tags: [Incidents] parameters: - $ref: '#/components/parameters/Tenant' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/Incident' } examples: start-incident: value: incidentId: inc-telemetry-outage kind: outage severity: major startedAt: 2025-11-17T04:02:00Z shortDescription: "Telemetry pipeline degraded; burn-rate breach" metadata: source: slo-evaluator responses: '202': description: Incident accepted default: $ref: '#/components/responses/Error' /api/v1/notify/incidents/{incidentId}/ack: post: summary: Acknowledge an incident notification tags: [Incidents] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/IncidentId' requestBody: required: true content: application/json: schema: type: object properties: ackToken: type: string description: DSSE-signed acknowledgement token responses: '204': description: Acknowledged default: $ref: '#/components/responses/Error' /api/v1/notify/quiet-hours: get: summary: Get quiet-hours schedule tags: [QuietHours] parameters: - $ref: '#/components/parameters/Tenant' responses: '200': description: Quiet hours schedule content: application/json: schema: { $ref: '#/components/schemas/QuietHours' } examples: current: value: quietHoursId: qh-default windows: - timezone: UTC days: [Mon, Tue, Wed, Thu, Fri] start: "22:00" end: "06:00" exemptions: - eventKinds: [attestor.verification.failed] reason: "Always alert for attestation failures" default: $ref: '#/components/responses/Error' post: summary: Set quiet-hours schedule tags: [QuietHours] parameters: - $ref: '#/components/parameters/Tenant' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/QuietHours' } responses: '200': description: Updated quiet hours content: application/json: schema: { $ref: '#/components/schemas/QuietHours' } default: $ref: '#/components/responses/Error' /api/v1/notify/pack-approvals: post: summary: Ingest pack approval decision tags: [PackApprovals] operationId: ingestPackApproval security: - oauth2: [notify.operator] - hmac: [] parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/PackApprovalEvent' } examples: approval-granted: value: eventId: "20e4e5fe-3d4a-4f57-9f9b-b1a1c1111111" issuedAt: "2025-11-17T16:00:00Z" kind: "pack.approval.granted" packId: "offline-kit-2025-11" policy: id: "policy-123" version: "v5" decision: "approved" actor: "task-runner" resumeToken: "rt-abc123" summary: "All required attestations verified." labels: environment: "prod" approver: "ops" responses: '202': description: Accepted; durable write queued for processing. headers: X-Resume-After: description: Resume token echo or replacement schema: { type: string } default: $ref: '#/components/responses/Error' /api/v1/notify/pack-approvals/{packId}/ack: post: summary: Acknowledge a pack approval notification tags: [PackApprovals] operationId: ackPackApproval parameters: - $ref: '#/components/parameters/Tenant' - name: packId in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object properties: ackToken: { type: string } required: [ackToken] responses: '204': description: Acknowledged default: $ref: '#/components/responses/Error' components: securitySchemes: oauth2: type: oauth2 flows: clientCredentials: tokenUrl: https://auth.stellaops.example.com/oauth/token scopes: notify.viewer: Read-only Notifier access notify.operator: Manage rules/templates/incidents within tenant notify.admin: Tenant-scoped administration hmac: type: http scheme: bearer description: Pre-shared HMAC token (air-gap friendly) referenced by secretRef. parameters: Tenant: name: X-StellaOps-Tenant in: header required: true description: Tenant slug schema: { type: string } IdempotencyKey: name: Idempotency-Key in: header required: true description: Stable UUID to dedupe retries. schema: { type: string, format: uuid } PageSize: name: pageSize in: query schema: { type: integer, minimum: 1, maximum: 200, default: 50 } PageToken: name: pageToken in: query schema: { type: string } RuleId: name: ruleId in: path required: true schema: { type: string } TemplateId: name: templateId in: path required: true schema: { type: string } IncidentId: name: incidentId in: path required: true schema: { type: string } responses: Error: description: Standard error envelope content: application/json: schema: { $ref: '#/components/schemas/ErrorEnvelope' } examples: validation: value: error: code: validation_failed message: "quietHours.windows[0].start must be HH:mm" traceId: "f62f3c2b9c8e4c53" schemas: ErrorEnvelope: type: object required: [error] properties: error: type: object required: [code, message, traceId] properties: code: { type: string } message: { type: string } traceId: { type: string } NotifyRule: type: object required: [ruleId, tenantId, name, match, actions] properties: ruleId: { type: string } tenantId: { type: string } name: { type: string } description: { type: string } enabled: { type: boolean, default: true } match: { $ref: '#/components/schemas/RuleMatch' } actions: type: array items: { $ref: '#/components/schemas/RuleAction' } labels: type: object additionalProperties: { type: string } metadata: type: object additionalProperties: { type: string } RuleMatch: type: object properties: eventKinds: type: array items: { type: string } minSeverity: { type: string, enum: [info, low, medium, high, critical] } verdicts: type: array items: { type: string } labels: type: array items: { type: string } kevOnly: { type: boolean } RuleAction: type: object required: [actionId, channel] properties: actionId: { type: string } channel: { type: string } template: { type: string } digest: { type: string, description: "Digest window key e.g. instant|5m|15m|1h|1d" } throttle: { type: string, description: "ISO-8601 duration, e.g. PT5M" } locale: { type: string } enabled: { type: boolean, default: true } metadata: type: object additionalProperties: { type: string } NotifyTemplate: type: object required: [templateId, tenantId, key, channelType, locale, body, renderMode, format] properties: templateId: { type: string } tenantId: { type: string } key: { type: string } channelType: { type: string, enum: [slack, teams, email, webhook, custom] } locale: { type: string, description: "BCP-47, lower-case" } renderMode: { type: string, enum: [Markdown, Html, AdaptiveCard, PlainText, Json] } format: { type: string, enum: [slack, teams, email, webhook, json] } description: { type: string } body: { type: string } metadata: type: object additionalProperties: { type: string } Incident: type: object required: [incidentId, kind, severity, startedAt] properties: incidentId: { type: string } kind: { type: string, description: "outage|degradation|security|ops-drill" } severity: { type: string, enum: [minor, major, critical] } startedAt: { type: string, format: date-time } endedAt: { type: string, format: date-time } shortDescription: { type: string } description: { type: string } metadata: type: object additionalProperties: { type: string } PackApprovalEvent: type: object required: - eventId - issuedAt - kind - packId - decision - actor properties: eventId: { type: string, format: uuid } issuedAt: { type: string, format: date-time } kind: type: string enum: [pack.approval.granted, pack.approval.denied, pack.policy.override] packId: { type: string } policy: type: object properties: id: { type: string } version: { type: string } decision: type: string enum: [approved, denied, overridden] actor: { type: string } resumeToken: type: string description: Opaque token for at-least-once resume. summary: { type: string } labels: type: object additionalProperties: { type: string } QuietHours: type: object required: [quietHoursId, windows] properties: quietHoursId: { type: string } windows: type: array items: { $ref: '#/components/schemas/QuietHoursWindow' } exemptions: type: array description: Event kinds that bypass quiet hours items: type: object properties: eventKinds: type: array items: { type: string } reason: { type: string } QuietHoursWindow: type: object required: [timezone, days, start, end] properties: timezone: { type: string, description: "IANA TZ, e.g., UTC" } days: type: array items: type: string enum: [Mon, Tue, Wed, Thu, Fri, Sat, Sun] start: { type: string, description: "HH:mm" } end: { type: string, description: "HH:mm" }