{
  "openapi": "3.1.0",
  "info": {
    "title": "AgentReady API",
    "version": "1.0.0",
    "description": "Agent-readiness scanning API. Scan a domain (or MCP endpoint), read cached scores, re-verify individual checks after fixes, browse the check catalog and leaderboard, and embed score badges. Anonymous use is allowed with IP-bucketed rate limits; send `Authorization: Bearer <api key>` for keyed limits. Every scan-shaped response embeds `contractVersion` — this document's version tracks that contract."
  },
  "servers": [
    {
      "url": "https://agentready-rho.vercel.app",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "scan",
      "description": "Run scans and re-verify checks"
    },
    {
      "name": "results",
      "description": "Read cached results"
    },
    {
      "name": "catalog",
      "description": "The static check catalog"
    },
    {
      "name": "leaderboard",
      "description": "Public rankings"
    },
    {
      "name": "badge",
      "description": "Embeddable SVG badges"
    }
  ],
  "paths": {
    "/api/scan": {
      "post": {
        "tags": [
          "scan"
        ],
        "operationId": "runScan",
        "summary": "Run (or reuse) a full scan",
        "description": "Scans a website or MCP endpoint for agent readiness. If a fresh-enough cached result exists (freshness window `maxAgeSeconds`, clamped server-side to 3600–86400 seconds, default 21600) it is returned immediately with `200` and an `Age` header. Otherwise the scan runs asynchronously: the response is `202` with a `Location` header pointing at `/api/scan/status/{scanId}` (poll until it returns the full result) and a `stream` field with an SSE progress URL (`/api/scan/stream?scanId=...`). If a scan for the same domain is already in flight you get `202` for that existing scan instead of a duplicate run.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScanRequest"
              },
              "example": {
                "url": "example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fresh-enough cached result served without re-scanning.",
            "headers": {
              "Age": {
                "description": "Seconds since the cached scan completed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanResult"
                }
              }
            }
          },
          "202": {
            "description": "Scan started (or already in progress). Poll the `Location` URL until it returns a complete ScanResult, or connect to the SSE stream for live progress.",
            "headers": {
              "Location": {
                "description": "Polling URL: `/api/scan/status/{scanId}`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanAccepted"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body, request shape (`details` carries zod issues), or target URL (`code: INVALID_URL` / `UNSUPPORTED_URL`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "`code: EPHEMERAL_CLOBBER` — an ephemeral scan may not overwrite a public domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Scan failed to start.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/scan/status/{scanId}": {
      "get": {
        "tags": [
          "scan"
        ],
        "operationId": "getScanStatus",
        "summary": "Poll an in-flight scan",
        "description": "The `Location` target of a `202` from `POST /api/scan`. Returns the full ScanResult once the scan completes; until then, a small status envelope.",
        "parameters": [
          {
            "name": "scanId",
            "in": "path",
            "required": true,
            "description": "Scan id (UUID) from the `202` response.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Complete result, or current status while running.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ScanResult"
                    },
                    {
                      "$ref": "#/components/schemas/ScanStatus"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Malformed scan id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Unknown scan id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/scan/checks": {
      "post": {
        "tags": [
          "scan"
        ],
        "operationId": "runChecks",
        "summary": "Re-verify a subset of checks",
        "description": "Runs the listed checks live against the target — the post-fix verification step. Always fetches fresh (never served from cache) and returns per-check results only; no aggregate score is computed and the cached scan/leaderboard entry is untouched. Check ids come from `GET /api/checks`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunChecksRequest"
              },
              "example": {
                "url": "example.com",
                "checkIds": [
                  "access.llms-txt",
                  "access.robots-txt"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-check live results.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunChecksResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body, request shape, or unknown check ids.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/score/{domain}": {
      "get": {
        "tags": [
          "results"
        ],
        "operationId": "getScore",
        "summary": "Read the cached score for a domain",
        "description": "Read-only cached lookup — never triggers a scan. `404` with `code: DOMAIN_NOT_SCANNED` includes a `nextAction` describing the `POST /api/scan` call that would generate a score.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "Hostname (e.g. `example.com`); normalized server-side.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest completed scan. Cached at the edge (`s-maxage=3600, stale-while-revalidate=600`).",
            "headers": {
              "Age": {
                "description": "Seconds since the scan completed.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "`code: DOMAIN_NOT_SCANNED` — no cached score. `nextAction` points at `POST /api/scan` with the normalized target as body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "example": {
                  "error": "No cached score for this domain",
                  "code": "DOMAIN_NOT_SCANNED",
                  "nextAction": {
                    "description": "Run a scan to generate a score",
                    "method": "POST",
                    "url": "https://agentready-rho.vercel.app/api/scan",
                    "body": {
                      "url": "example.com"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/checks": {
      "get": {
        "tags": [
          "catalog"
        ],
        "operationId": "getChecks",
        "summary": "The complete check catalog",
        "description": "Every check the scanner can run: stable ids, layers, weights, tiers, maturity, methods, and fix recommendations. Byte-stable between contract versions — gate CI on explicit check-id lists. Statically served with `s-maxage=3600, stale-while-revalidate=86400` and `Access-Control-Allow-Origin: *`.",
        "responses": {
          "200": {
            "description": "The catalog for the current contract version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckCatalog"
                }
              }
            }
          }
        }
      }
    },
    "/api/leaderboard": {
      "get": {
        "tags": [
          "leaderboard"
        ],
        "operationId": "getLeaderboard",
        "summary": "Public leaderboard",
        "description": "Ranked, publicly scanned domains. Cached at the edge (`s-maxage=300, stale-while-revalidate=600`).",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Category slug filter; unknown slugs return `400` listing valid slugs.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring search over domain/name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max entries (default 25, capped at 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked entries plus the total ranked count.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LeaderboardResponse"
                }
              }
            }
          },
          "400": {
            "description": "Unknown category slug.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/badge/{domain}": {
      "get": {
        "tags": [
          "badge"
        ],
        "operationId": "getBadge",
        "summary": "SVG score badge",
        "description": "Shields-style SVG badge with the domain's latest score and grade. Never triggers a scan: unscanned, ephemeral, or hidden domains get a neutral “not scanned” badge with `404`. Cached with `s-maxage=3600, stale-while-revalidate=86400` (`s-maxage=300` for the 404 badge). Embed: `![AgentReady score](https://agentready-rho.vercel.app/api/badge/example.com)`.",
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "description": "Hostname (e.g. `example.com`).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Score badge, e.g. `agentready | 92 A`.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid hostname — an `invalid` badge is still rendered.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "Domain not (publicly) scanned — a neutral `not scanned` badge.",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Optional API key for keyed rate limits. Anonymous access works."
      }
    },
    "responses": {
      "RateLimited": {
        "description": "`code: RATE_LIMIT_EXCEEDED` — IP or key bucket exhausted. `retryAfterSeconds` in the body and a `Retry-After` header say when to try again.",
        "headers": {
          "Retry-After": {
            "description": "Seconds until the bucket resets.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "CheckStatus": {
        "type": "string",
        "enum": [
          "pass",
          "fail",
          "warning",
          "error",
          "pending",
          "na"
        ],
        "description": "`pass`/`fail`/`warning` are scored outcomes; `error` means the check could not run; `pending` is still resolving (phase B); `na` does not apply to this target and is dropped from the denominator."
      },
      "LayerId": {
        "type": "string",
        "enum": [
          "discovery",
          "access",
          "usability",
          "payments"
        ]
      },
      "CheckTier": {
        "type": "string",
        "enum": [
          "required",
          "recommended",
          "emerging"
        ]
      },
      "CheckMaturity": {
        "type": "string",
        "enum": [
          "verified",
          "emerging"
        ],
        "description": "Emerging checks are displayed but never scored."
      },
      "UrlKind": {
        "type": "string",
        "enum": [
          "domain",
          "mcp",
          "mcp-app",
          "ephemeral"
        ]
      },
      "AnalysisStatus": {
        "type": "string",
        "enum": [
          "complete",
          "partial",
          "stuck"
        ],
        "description": "`partial` means phase-B (LLM/registry) checks are still resolving; `stuck` means they never resolved."
      },
      "NextAction": {
        "type": "object",
        "description": "A machine-followable pointer to the call that unblocks the caller.",
        "required": [
          "description",
          "method",
          "url"
        ],
        "properties": {
          "description": {
            "type": "string"
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "POST"
            ]
          },
          "url": {
            "type": "string"
          },
          "body": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "CheckResult": {
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "score",
          "maxScore",
          "tier",
          "maturity"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable check identifier (e.g. `access.llms-txt`). Route fixes and dedupe by this."
          },
          "name": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/CheckStatus"
          },
          "score": {
            "type": "number",
            "description": "Points earned WITHIN this layer (not 0-100 points)."
          },
          "maxScore": {
            "type": "number",
            "description": "Points available within this layer for this check."
          },
          "tier": {
            "$ref": "#/components/schemas/CheckTier"
          },
          "maturity": {
            "$ref": "#/components/schemas/CheckMaturity"
          },
          "bonus": {
            "type": "boolean",
            "description": "Bonus checks can only raise a score."
          },
          "beta": {
            "type": "boolean"
          },
          "estScoreGain": {
            "type": "number",
            "description": "Estimated 0-100 score uplift if this check were fixed. Present on fail/warning."
          },
          "specUrl": {
            "type": "string"
          },
          "details": {
            "type": "string",
            "description": "What the scan observed."
          },
          "method": {
            "type": "string",
            "description": "One-line description of how the check was performed."
          },
          "recommendation": {
            "type": "string",
            "description": "Concrete fix that would make this check pass."
          },
          "naReason": {
            "type": "string",
            "description": "Why this check does not apply (status `na` only)."
          }
        }
      },
      "LayerResult": {
        "type": "object",
        "required": [
          "id",
          "name",
          "score",
          "maxScore",
          "checks"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/LayerId"
          },
          "name": {
            "type": "string"
          },
          "score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Normalized: 0..weight. Null when no countable evidence in this layer."
          },
          "maxScore": {
            "type": "number",
            "description": "The layer weight."
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckResult"
            }
          }
        }
      },
      "TopFix": {
        "type": "object",
        "required": [
          "checkId",
          "name",
          "estScoreGain",
          "recommendation"
        ],
        "properties": {
          "checkId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "estScoreGain": {
            "type": "number"
          },
          "recommendation": {
            "type": "string"
          }
        }
      },
      "ScanResult": {
        "type": "object",
        "description": "A complete (or provisional) scan result. Mirrors `scanResultSchema`.",
        "required": [
          "domain",
          "name",
          "score",
          "scoreMax",
          "grade",
          "gradeColor",
          "ctaMessage",
          "scannedAt",
          "durationMs",
          "analysisStatus",
          "pendingChecks",
          "layers",
          "topFixes",
          "url",
          "generatedAt",
          "source",
          "contractVersion"
        ],
        "properties": {
          "domain": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "score": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "scoreMax": {
            "type": "integer",
            "const": 100
          },
          "grade": {
            "type": "string",
            "description": "Letter grade, e.g. `A-`."
          },
          "gradeColor": {
            "type": "string",
            "description": "Hex color for the grade."
          },
          "ctaMessage": {
            "type": [
              "string",
              "null"
            ]
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time"
          },
          "durationMs": {
            "type": [
              "integer",
              "null"
            ]
          },
          "analysisStatus": {
            "$ref": "#/components/schemas/AnalysisStatus"
          },
          "pendingChecks": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids of checks still resolving; empty when analysisStatus is complete."
          },
          "layers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LayerResult"
            }
          },
          "topFixes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TopFix"
            },
            "description": "Actionable checks ranked: non-bonus first, then estScoreGain desc, capped at 6."
          },
          "url": {
            "type": "string",
            "description": "Canonical report deep link."
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "source": {
            "type": "string",
            "const": "agentready"
          },
          "contractVersion": {
            "type": "string"
          },
          "urlKind": {
            "$ref": "#/components/schemas/UrlKind"
          },
          "category": {
            "type": "string"
          },
          "agenticSummary": {
            "type": "string",
            "description": "One-sentence LLM verdict; absent on partial results."
          },
          "finalUrl": {
            "type": "string",
            "description": "The URL the scan actually fetched after redirects."
          },
          "servedFromCache": {
            "type": "boolean"
          },
          "resultAgeSeconds": {
            "type": "integer"
          },
          "mcpAuthRequired": {
            "type": "boolean"
          },
          "nextAction": {
            "$ref": "#/components/schemas/NextAction"
          }
        }
      },
      "ScanRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "description": "Domain or URL to scan, e.g. `example.com`."
          },
          "mcpUrl": {
            "type": "string",
            "description": "MCP endpoint to scan alongside (or as) the target."
          },
          "maxAgeSeconds": {
            "type": "integer",
            "description": "Freshness window in seconds; clamped server-side to [3600, 86400]. Default 21600."
          },
          "force": {
            "type": "boolean",
            "description": "Bypass the cache and re-scan (tighter rate bucket)."
          },
          "ephemeral": {
            "type": "boolean",
            "description": "For tunnels/previews: result is excluded from the leaderboard and directory."
          }
        }
      },
      "ScanAccepted": {
        "type": "object",
        "description": "`202` body while a scan runs.",
        "required": [
          "scanId",
          "domain",
          "status"
        ],
        "properties": {
          "scanId": {
            "type": "string",
            "format": "uuid"
          },
          "domain": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "const": "running"
          },
          "stream": {
            "type": "string",
            "description": "SSE progress URL (`/api/scan/stream?scanId=...`). Present when this request started the scan."
          },
          "message": {
            "type": "string",
            "description": "Present when a scan for this domain was already in progress."
          }
        }
      },
      "ScanStatus": {
        "type": "object",
        "description": "Poll envelope while a scan has not completed.",
        "required": [
          "scanId",
          "status"
        ],
        "properties": {
          "scanId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "description": "e.g. `running`, `partial`, `error`, `stuck`."
          },
          "pendingChecks": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "code": {
            "type": "string",
            "description": "Error code when status is `error` (e.g. `INSUFFICIENT_EVIDENCE`)."
          }
        }
      },
      "RunChecksRequest": {
        "type": "object",
        "required": [
          "url",
          "checkIds"
        ],
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1
          },
          "checkIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Stable check ids from `GET /api/checks`."
          },
          "mcpUrl": {
            "type": "string"
          }
        }
      },
      "RunChecksResult": {
        "type": "object",
        "required": [
          "id",
          "status",
          "score",
          "maxScore"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/CheckStatus"
          },
          "score": {
            "type": "number"
          },
          "maxScore": {
            "type": "number"
          },
          "details": {
            "type": "string"
          },
          "recommendation": {
            "type": "string"
          },
          "naReason": {
            "type": "string"
          }
        }
      },
      "RunChecksResponse": {
        "type": "object",
        "required": [
          "url",
          "results"
        ],
        "properties": {
          "url": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RunChecksResult"
            }
          }
        }
      },
      "LeaderboardEntry": {
        "type": "object",
        "required": [
          "rank",
          "domain",
          "name",
          "category",
          "score",
          "grade",
          "scannedAt",
          "reportUrl"
        ],
        "properties": {
          "rank": {
            "type": "integer"
          },
          "domain": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "score": {
            "type": "number"
          },
          "grade": {
            "type": "string"
          },
          "scannedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "reportUrl": {
            "type": "string"
          }
        }
      },
      "LeaderboardResponse": {
        "type": "object",
        "required": [
          "entries",
          "totalRanked"
        ],
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LeaderboardEntry"
            }
          },
          "totalRanked": {
            "type": "integer"
          }
        }
      },
      "CheckCatalogEntry": {
        "type": "object",
        "required": [
          "id",
          "name",
          "layer",
          "goal",
          "maxScore",
          "tier",
          "maturity",
          "bonus",
          "beta",
          "phase",
          "kinds",
          "method",
          "recommendation"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable id, e.g. `access.llms-txt`."
          },
          "name": {
            "type": "string"
          },
          "layer": {
            "$ref": "#/components/schemas/LayerId"
          },
          "goal": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10
          },
          "maxScore": {
            "type": "number"
          },
          "tier": {
            "$ref": "#/components/schemas/CheckTier"
          },
          "maturity": {
            "$ref": "#/components/schemas/CheckMaturity"
          },
          "bonus": {
            "type": "boolean"
          },
          "beta": {
            "type": "boolean"
          },
          "phase": {
            "type": "string",
            "enum": [
              "A",
              "B"
            ],
            "description": "A = deterministic fetch/parse; B = LLM or registry backed."
          },
          "kinds": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "domain",
                "mcp",
                "mcp-app"
              ]
            },
            "description": "Target kinds this check applies to."
          },
          "specUrl": {
            "type": "string"
          },
          "method": {
            "type": "string"
          },
          "recommendation": {
            "type": "string"
          }
        }
      },
      "CheckCatalog": {
        "type": "object",
        "required": [
          "contractVersion",
          "layers",
          "goals",
          "checks"
        ],
        "properties": {
          "contractVersion": {
            "type": "string"
          },
          "layers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "weight"
              ],
              "properties": {
                "id": {
                  "$ref": "#/components/schemas/LayerId"
                },
                "name": {
                  "type": "string"
                },
                "weight": {
                  "type": "number",
                  "description": "Weights sum to 100."
                }
              }
            }
          },
          "goals": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "title"
              ],
              "properties": {
                "id": {
                  "type": "integer"
                },
                "title": {
                  "type": "string"
                }
              }
            }
          },
          "checks": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CheckCatalogEntry"
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "The uniform error envelope. Mirrors `errorResponseSchema`.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "code": {
            "type": "string",
            "description": "Machine-routable code: `RATE_LIMIT_EXCEEDED`, `DOMAIN_NOT_SCANNED`, `INVALID_URL`, `UNSUPPORTED_URL`, `EPHEMERAL_CLOBBER`, `INSUFFICIENT_EVIDENCE`, `SCAN_FAILED`."
          },
          "retryAfterSeconds": {
            "type": "number"
          },
          "nextAction": {
            "$ref": "#/components/schemas/NextAction"
          },
          "details": {
            "description": "Validation issues or debug detail."
          }
        }
      }
    }
  }
}