{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/rendex-dev/json-schema/rendex-dev-watch-schema.json",
  "title": "Watch",
  "description": "A monitoring configuration. webhookUrl is redacted to its host on read.",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/rendex-dev-openapi.yaml#/components/schemas/Watch",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "url": {
      "type": "string",
      "format": "uri"
    },
    "name": {
      "type": "string",
      "description": "Null when unset"
    },
    "intervalMinutes": {
      "type": "integer"
    },
    "diffMode": {
      "type": "string",
      "enum": [
        "visual",
        "text",
        "both"
      ]
    },
    "threshold": {
      "type": "number",
      "description": "0..1 fraction"
    },
    "renderParams": {
      "type": "object"
    },
    "aiSummary": {
      "type": "boolean"
    },
    "webhookUrl": {
      "type": "string",
      "description": "Redacted to host; null when no webhook channel"
    },
    "notifyEmail": {
      "type": "string",
      "description": "Null when unset"
    },
    "status": {
      "type": "string",
      "enum": [
        "active",
        "paused"
      ]
    },
    "baselineImageUrl": {
      "type": "string",
      "format": "uri",
      "description": "Short-lived signed URL of the watch's current baseline — the \"before\" image used in the next change comparison; null until a baseline exists"
    },
    "baselineCapturedAt": {
      "type": "string",
      "format": "date-time"
    },
    "lastRunAt": {
      "type": "string",
      "format": "date-time"
    },
    "nextRunAt": {
      "type": "string",
      "format": "date-time",
      "description": "Null while paused"
    },
    "lastChangedAt": {
      "type": "string",
      "format": "date-time"
    },
    "lastStatus": {
      "type": "string",
      "description": "completed | failed | null"
    },
    "consecutiveFailures": {
      "type": "integer"
    },
    "uaBlockedAt": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "When the target last blocked the RendexWatch identity; null if never"
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time"
    },
    "lastChange": {
      "type": [
        "object",
        "null"
      ],
      "description": "The \"what changed\" enrichment from this watch's most recent CHANGED run — returned on GET /v1/watches and GET /v1/watches/{id} so a polling \"changed\" trigger gets the detail inline (no follow-up call to /:id/runs). Null when the watch has never changed.",
      "properties": {
        "aiSummary": {
          "type": [
            "string",
            "null"
          ],
          "description": "One-sentence \"what changed\" summary (templated by default; AI-written when a Pro+ watch opted in)"
        },
        "textDiff": {
          "type": [
            "object",
            "null"
          ],
          "description": "Parsed text diff: { added, removed, summary }. Null for a visual-only change"
        }
      }
    }
  }
}