{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/rendex-dev/json-schema/rendex-dev-watch-run-schema.json",
  "title": "WatchRun",
  "description": "One execution of a watch. Image URLs are short-lived signed links and may 404 for runs older than the bucket lifecycle.",
  "x-generated": "2026-10-31",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/rendex-dev-openapi.yaml#/components/schemas/WatchRun",
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "watchId": {
      "type": "string",
      "format": "uuid"
    },
    "status": {
      "type": "string",
      "enum": [
        "queued",
        "processing",
        "completed",
        "failed"
      ]
    },
    "changed": {
      "type": "boolean",
      "description": "Null until the run completes"
    },
    "diffScore": {
      "type": "number",
      "description": "0..1 fraction of the page that changed; null for text-only or failed runs"
    },
    "diffPixels": {
      "type": "integer"
    },
    "beforeUrl": {
      "type": "string",
      "format": "uri",
      "description": "Signed \"before\" baseline capture"
    },
    "afterUrl": {
      "type": "string",
      "format": "uri",
      "description": "Signed \"after\" capture"
    },
    "diffOverlayUrl": {
      "type": "string",
      "format": "uri",
      "description": "Signed highlighted overlay"
    },
    "cropUrl": {
      "type": [
        "string",
        "null"
      ],
      "format": "uri",
      "description": "Signed image cropped tight to the change (\"what changed\") — post straight into Slack/a ticket. Null when the run had no discrete visual region (dimensions-only or text-only change)."
    },
    "changedRegion": {
      "allOf": [
        {
          "$ref": "#/$defs/WatchChangedRegion"
        }
      ],
      "nullable": true,
      "description": "Normalized (0..1) union box of the changed regions. Null when there is no discrete region."
    },
    "aiSummary": {
      "type": [
        "string",
        "null"
      ],
      "description": "One-line plain-English \"what changed\" summary (Pro+; AI from the text diff, or a templated line from the changed region for visual-only changes). Same value the watch.changed webhook `summary` field carries. Null when unavailable."
    },
    "textDiff": {
      "type": "object",
      "description": "Present for text/both runs; null otherwise",
      "properties": {
        "added": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "removed": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "summary": {
          "type": "string"
        }
      }
    },
    "creditsCharged": {
      "type": "integer",
      "description": "Net credits charged for this run (0 after a refund or for the demo account)"
    },
    "error": {
      "type": "string",
      "description": "Free-text failure reason when status is failed"
    },
    "errorCode": {
      "type": "string",
      "enum": [
        "blocked",
        "timeout",
        "selector",
        "http_error",
        "network",
        "storage",
        "unknown"
      ],
      "description": "Structured failure reason; null until/unless the run fails. Branch on this instead of parsing error."
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    },
    "completedAt": {
      "type": "string",
      "format": "date-time"
    }
  },
  "$defs": {
    "WatchChangedRegion": {
      "type": "object",
      "description": "Normalized (0..1) union bounding box of all changed regions on the page — the \"where\" of a visual change. Multiply by the capture's dimensions to get pixels; use it to crop or annotate in your own tooling.",
      "properties": {
        "x": {
          "type": "number",
          "description": "Left edge as a fraction of page width (0..1)."
        },
        "y": {
          "type": "number",
          "description": "Top edge as a fraction of page height (0..1)."
        },
        "width": {
          "type": "number",
          "description": "Width as a fraction of page width (0..1)."
        },
        "height": {
          "type": "number",
          "description": "Height as a fraction of page height (0..1)."
        }
      }
    }
  }
}