{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://deployradar.dev/schemas/scan-v1.json",
  "title": "DeployRadar /api/v1/scan response",
  "description": "The successful response of GET /api/v1/scan, or its error form. Fields may be added under v1; none will be removed or change meaning. Validators should allow unknown properties inside nested objects.",
  "oneOf": [
    {
      "$ref": "#/$defs/Scan"
    },
    {
      "$ref": "#/$defs/Error"
    }
  ],
  "$defs": {
    "Error": {
      "type": "object",
      "required": [
        "apiVersion",
        "error",
        "docs"
      ],
      "properties": {
        "apiVersion": {
          "const": 1
        },
        "error": {
          "type": "object",
          "required": [
            "code",
            "message"
          ],
          "properties": {
            "code": {
              "enum": [
                "missing_url",
                "invalid_target",
                "rate_limited",
                "at_capacity",
                "scan_failed"
              ]
            },
            "message": {
              "type": "string"
            }
          }
        },
        "docs": {
          "type": "string"
        }
      }
    },
    "Scan": {
      "type": "object",
      "required": [
        "apiVersion",
        "target",
        "scannedAt",
        "cached",
        "registryVersion",
        "provenance",
        "health",
        "plan",
        "groups",
        "findings",
        "evidence",
        "site",
        "robotsStatus",
        "google",
        "parity",
        "parityReason",
        "render",
        "docs",
        "schema"
      ],
      "properties": {
        "apiVersion": {
          "const": 1
        },
        "target": {
          "type": "string"
        },
        "scannedAt": {
          "type": "string"
        },
        "cached": {
          "type": "boolean"
        },
        "registryVersion": {
          "type": "string"
        },
        "provenance": {
          "anyOf": [
            {
              "$ref": "#/$defs/Provenance"
            },
            {
              "type": "null"
            }
          ]
        },
        "health": {
          "anyOf": [
            {
              "$ref": "#/$defs/Health"
            },
            {
              "type": "null"
            }
          ]
        },
        "plan": {
          "anyOf": [
            {
              "$ref": "#/$defs/Plan"
            },
            {
              "type": "null"
            }
          ]
        },
        "groups": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/Group"
              }
            },
            {
              "type": "null"
            }
          ]
        },
        "findings": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/Finding"
              }
            },
            {
              "type": "null"
            }
          ]
        },
        "evidence": {
          "anyOf": [
            {
              "$ref": "#/$defs/Evidence"
            },
            {
              "type": "null"
            }
          ]
        },
        "site": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/SiteFinding"
              }
            },
            {
              "type": "null"
            }
          ]
        },
        "robotsStatus": {
          "type": [
            "integer",
            "null"
          ]
        },
        "google": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "$ref": "#/$defs/GoogleFinding"
              }
            },
            {
              "type": "null"
            }
          ]
        },
        "parity": {
          "anyOf": [
            {
              "$ref": "#/$defs/Parity"
            },
            {
              "type": "null"
            }
          ]
        },
        "parityReason": {
          "type": [
            "string",
            "null"
          ]
        },
        "render": {
          "anyOf": [
            {
              "$ref": "#/$defs/RenderStats"
            },
            {
              "type": "null"
            }
          ]
        },
        "docs": {
          "type": "string"
        },
        "schema": {
          "type": "string"
        }
      }
    },
    "Provenance": {
      "type": "object",
      "required": [
        "scannerVersion",
        "classifierVersion",
        "registryVersion"
      ],
      "properties": {
        "scannerVersion": {
          "type": "string"
        },
        "classifierVersion": {
          "type": "integer"
        },
        "registryVersion": {
          "type": "string"
        }
      }
    },
    "Health": {
      "type": "object",
      "required": [
        "robots",
        "baseline",
        "probes",
        "confidence",
        "notes"
      ],
      "properties": {
        "robots": {
          "enum": [
            "ok",
            "missing",
            "temporarily_unavailable",
            "error"
          ]
        },
        "baseline": {
          "enum": [
            "ok",
            "failed"
          ]
        },
        "probes": {
          "enum": [
            "complete",
            "partial",
            "none"
          ]
        },
        "confidence": {
          "enum": [
            "high",
            "medium",
            "low"
          ]
        },
        "notes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "consistent": {
          "type": "boolean"
        }
      }
    },
    "Plan": {
      "type": "object",
      "required": [
        "robots",
        "sitemap",
        "baseline",
        "control",
        "probes",
        "render",
        "fetches",
        "maxFetches"
      ],
      "properties": {
        "robots": {
          "const": true
        },
        "sitemap": {
          "type": "boolean"
        },
        "baseline": {
          "type": "boolean"
        },
        "control": {
          "type": "boolean"
        },
        "probes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "render": {
          "type": "boolean"
        },
        "fetches": {
          "type": "integer",
          "minimum": 0
        },
        "maxFetches": {
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "Group": {
      "type": "object",
      "required": [
        "groupId",
        "label",
        "status",
        "summary",
        "findings"
      ],
      "properties": {
        "groupId": {
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "status": {
          "enum": [
            "ok",
            "info",
            "warning",
            "blocked"
          ]
        },
        "summary": {
          "type": "string"
        },
        "findings": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/Finding"
          }
        }
      }
    },
    "Finding": {
      "type": "object",
      "required": [
        "token",
        "operator",
        "group",
        "policy",
        "policySource",
        "enforcement",
        "confidence",
        "consequence",
        "notes"
      ],
      "properties": {
        "token": {
          "type": "string"
        },
        "operator": {
          "type": "string"
        },
        "group": {
          "type": "string"
        },
        "purpose": {
          "enum": [
            "training",
            "search_citation",
            "user_fetch",
            "mixed",
            "corpus",
            "info_only"
          ]
        },
        "impact": {
          "$ref": "#/$defs/Impact"
        },
        "policy": {
          "enum": [
            "allow",
            "deny",
            "default"
          ]
        },
        "policySource": {
          "enum": [
            "token",
            "fallback",
            "wildcard",
            "none"
          ]
        },
        "matchedRule": {
          "type": "object",
          "required": [
            "type",
            "path",
            "agent"
          ],
          "properties": {
            "type": {
              "enum": [
                "allow",
                "disallow"
              ]
            },
            "path": {
              "type": "string"
            },
            "agent": {
              "type": "string"
            }
          }
        },
        "pathScope": {
          "type": "object",
          "required": [
            "agent",
            "blocked",
            "exceptions"
          ]
        },
        "enforcement": {
          "enum": [
            "not_tested",
            "no_ua_block_detected",
            "ua_block_confirmed",
            "possibly_blocked_edge",
            "pay_per_crawl",
            "inconclusive"
          ]
        },
        "confidence": {
          "enum": [
            "high",
            "medium",
            "low"
          ]
        },
        "challenge": {
          "type": [
            "string",
            "null"
          ]
        },
        "consequence": {
          "type": "string"
        },
        "notes": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "evidence": {
          "$ref": "#/$defs/FindingEvidence"
        }
      }
    },
    "Impact": {
      "type": "object",
      "required": [
        "search",
        "citations",
        "training",
        "agents"
      ],
      "properties": {
        "search": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "const": "unknown"
            }
          ]
        },
        "citations": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "const": "unknown"
            }
          ]
        },
        "training": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "const": "unknown"
            }
          ]
        },
        "agents": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "const": "unknown"
            }
          ]
        }
      }
    },
    "FindingEvidence": {
      "type": "object",
      "required": [
        "verdict",
        "level",
        "robots",
        "request"
      ],
      "properties": {
        "verdict": {
          "enum": [
            "accessible",
            "restricted",
            "blocked",
            "possibly_blocked",
            "unknown"
          ]
        },
        "level": {
          "enum": [
            "verified",
            "declared",
            "inferred",
            "unknown"
          ]
        },
        "reason": {
          "type": "object",
          "required": [
            "primary",
            "supporting"
          ],
          "properties": {
            "primary": {
              "type": "string",
              "pattern": "^[A-Z0-9_]+$"
            },
            "supporting": {
              "type": "array",
              "items": {
                "type": "string",
                "pattern": "^[A-Z0-9_]+$"
              }
            }
          }
        },
        "robots": {
          "type": "object",
          "required": [
            "read",
            "allowed"
          ],
          "properties": {
            "read": {
              "type": "boolean"
            },
            "allowed": {
              "type": "boolean"
            },
            "complete": {
              "type": "boolean"
            }
          }
        },
        "request": {
          "type": "object",
          "required": [
            "tested",
            "status",
            "baselineStatus"
          ],
          "properties": {
            "tested": {
              "type": "boolean"
            },
            "untestedReason": {
              "enum": [
                "token_only",
                "never_probe",
                "not_in_probe_set",
                "self_excluded"
              ]
            },
            "status": {
              "type": [
                "integer",
                "null"
              ]
            },
            "baselineStatus": {
              "type": [
                "integer",
                "null"
              ]
            },
            "userAgent": {
              "type": "string"
            },
            "finalUrl": {
              "type": "string"
            },
            "redirects": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/RedirectHop"
              }
            },
            "disposition": {
              "enum": [
                "content",
                "soft_block",
                "login",
                "error_page",
                "empty_shell",
                "different_content"
              ]
            },
            "failure": {
              "$ref": "#/$defs/Failure"
            }
          }
        }
      }
    },
    "Failure": {
      "enum": [
        "timeout",
        "dns_error",
        "tls_error",
        "connection_refused",
        "connection_reset",
        "too_many_redirects",
        "bad_redirect",
        "refused_by_guard",
        "not_sent",
        "network_error"
      ]
    },
    "RedirectHop": {
      "type": "object",
      "required": [
        "from",
        "status",
        "to"
      ],
      "properties": {
        "from": {
          "type": "string"
        },
        "status": {
          "type": "integer"
        },
        "to": {
          "type": "string"
        }
      }
    },
    "Evidence": {
      "type": "object",
      "required": [
        "robots",
        "requests"
      ],
      "properties": {
        "robots": {
          "type": "object",
          "required": [
            "status"
          ],
          "properties": {
            "status": {
              "type": [
                "integer",
                "null"
              ]
            },
            "bodyRead": {
              "$ref": "#/$defs/BodyRead"
            }
          }
        },
        "requests": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/RequestRecord"
          }
        }
      }
    },
    "BodyRead": {
      "enum": [
        "complete",
        "truncated",
        "failed"
      ]
    },
    "RequestRecord": {
      "type": "object",
      "required": [
        "role",
        "method",
        "url",
        "userAgent",
        "status",
        "headers"
      ],
      "properties": {
        "role": {
          "enum": [
            "baseline",
            "control",
            "probe"
          ]
        },
        "token": {
          "type": "string"
        },
        "method": {
          "const": "GET"
        },
        "url": {
          "type": "string"
        },
        "userAgent": {
          "type": "string"
        },
        "status": {
          "type": [
            "integer",
            "null"
          ]
        },
        "finalUrl": {
          "type": "string"
        },
        "redirects": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/RedirectHop"
          }
        },
        "bodyBytes": {
          "type": "integer"
        },
        "bodyRead": {
          "$ref": "#/$defs/BodyRead"
        },
        "error": {
          "type": "string"
        },
        "headers": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "repeat": {
          "type": "object",
          "required": [
            "status"
          ],
          "properties": {
            "status": {
              "type": [
                "integer",
                "null"
              ]
            },
            "error": {
              "type": "string"
            }
          }
        }
      }
    },
    "SiteFinding": {
      "type": "object",
      "required": [
        "id",
        "label",
        "status",
        "summary",
        "detail"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "status": {
          "enum": [
            "ok",
            "info",
            "warning",
            "blocked"
          ]
        },
        "summary": {
          "type": "string"
        },
        "detail": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "GoogleFinding": {
      "allOf": [
        {
          "$ref": "#/$defs/SiteFinding"
        }
      ],
      "properties": {
        "layer": {
          "enum": [
            "crawlability",
            "fetchability",
            "indexability",
            "canonicalization",
            "presentation"
          ]
        }
      }
    },
    "Parity": {
      "type": "object",
      "required": [
        "status",
        "textParity",
        "rawWords",
        "renderedWords",
        "measurable",
        "summary",
        "detail"
      ],
      "properties": {
        "status": {
          "enum": [
            "ok",
            "info",
            "warning",
            "blocked"
          ]
        },
        "textParity": {
          "type": "number"
        },
        "rawWords": {
          "type": "integer"
        },
        "renderedWords": {
          "type": "integer"
        },
        "measurable": {
          "type": "boolean"
        },
        "summary": {
          "type": "string"
        },
        "detail": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "RenderStats": {
      "type": "object",
      "required": [
        "requests",
        "blocked",
        "bytes",
        "durationMs",
        "capped"
      ],
      "properties": {
        "requests": {
          "type": "integer"
        },
        "blocked": {
          "type": "integer"
        },
        "bytes": {
          "type": "integer"
        },
        "durationMs": {
          "type": "integer"
        },
        "capped": {
          "type": "boolean"
        }
      }
    }
  }
}
