{
  "openapi": "3.1.0",
  "info": {
    "title": "GDPR Scan MD API",
    "version": "2026-07-28",
    "summary": "Deterministic GDPR / Legea 195/2024 website compliance scanning for Moldova.",
    "description": "Deterministic GDPR / Legea 195/2024 compliance scanner for Moldova. No AI, no heuristics with a temperature: every finding is a measurement of the live page, reproducible from the same inputs.\n\n**Free.** There is no paid tier and no billing. A key is issued on request through the lead form on any report page.\n\n**The verdict `inconclusiv` is part of the contract.** When a page does not render well enough to audit, the API returns `verdict: \"inconclusiv\"`, `score: null` and an empty `findings` array, together with the `confidence` object showing the measurements behind that decision. An automated consumer must be able to tell \"this site is non-compliant\" from \"we could not measure this site\"; conflating the two is the defect this product exists to correct.\n\n**Versioning.** Breaking changes get a new path prefix (`/api/v2`), never a change in place. New optional fields and new members of the open enumerations (finding ids, confidence reasons, legal regime ids) may appear at any time; clients must tolerate them. If v1 is ever retired, `Deprecation` and `Sunset` headers appear on every response at least 12 months beforehand.\n\n**Rate limits.** 5 new scans per 10 minutes per client IP, shared with the website's own scanner. A given host is scanned afresh at most once every 15 minutes; a request inside that window is answered from the last report with `X-Scan-Cache: hit` and does not consume the budget.\n\n**Keys are server-side only.** CORS is not enabled: a key that can aim a browser at arbitrary third-party sites does not belong in a web page.",
    "contact": {
      "name": "MEGA PROMOTING S.R.L.",
      "email": "oleg@megapromoting.com",
      "url": "https://megapromoting.com"
    }
  },
  "servers": [
    {
      "url": "https://ongdpr.md/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "scans",
      "description": "Create and read compliance scans."
    }
  ],
  "externalDocs": {
    "description": "Human documentation (RO/RU)",
    "url": "https://ongdpr.md/ro/api"
  },
  "paths": {
    "/scans": {
      "post": {
        "tags": [
          "scans"
        ],
        "operationId": "createScan",
        "summary": "Start a scan",
        "description": "Enqueues a scan and returns immediately with a job id. Scans take 5-45 seconds; poll `GET /scans/{id}`.\n\nSend `Idempotency-Key` to make a retry safe: the same key with the same body returns the original job (HTTP 200, `Idempotent-Replay: true`) instead of starting a second scan of somebody else's website; the same key with a different body is a 409. Keys are remembered for 24 hours and, because they are held in process memory, do not survive a restart of the service.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Response format. `sarif` emits a SARIF 2.1.0 log valid against the OASIS schema; `csv` emits an RFC 4180 document with one row per finding and a stable, append-only header.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv",
                "sarif"
              ],
              "default": "json"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Client-generated unique string, at most 255 characters. Scoped to your API key.",
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Absolute http(s) URL of the page to audit.",
                    "examples": [
                      "https://exemplu.md"
                    ]
                  }
                }
              },
              "example": {
                "url": "https://exemplu.md"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "No new scan was started. Either an idempotent replay (`Idempotent-Replay: true`) or a hit on the 15-minute per-host freshness cache (`X-Scan-Cache: hit`). The body is the existing job.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              },
              "Idempotent-Replay": {
                "description": "Present and `true` on a replay.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Scan-Cache": {
                "description": "Present and `hit` when served from the freshness cache.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Scan-Cache-Expires": {
                "description": "When this host may be scanned afresh again.",
                "schema": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanJob"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "\"scan_id\",\"scan_status\",\"requested_url\",\"final_url\",\"scanned_at_utc\",\"verdict\",\"score\",\"confidence_sufficient\",\"confidence_reason\",\"confidence_html_length\",\"confidence_anchor_count\",\"confidence_dom_node_count\",\"finding_id\",\"category\",\"severity\",\"status\",\"weight\",\"title_ro\",\"title_ru\",\"detail_ro\",\"detail_ru\",\"remediation_ro\",\"remediation_ru\",\"legal_reference\",\"legal_classification\",\"legal_applicability\",\"legal_evidence_strength\",\"legal_confidence\",\"legal_regimes\",\"legal_citations\",\"evidence\""
              },
              "application/sarif+json": {
                "schema": {
                  "type": "object",
                  "description": "SARIF 2.1.0 log (OASIS schema)."
                }
              }
            }
          },
          "202": {
            "description": "Scan accepted and queued.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              },
              "Location": {
                "description": "Where to poll for the result.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanJob"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "\"scan_id\",\"scan_status\",\"requested_url\",\"final_url\",\"scanned_at_utc\",\"verdict\",\"score\",\"confidence_sufficient\",\"confidence_reason\",\"confidence_html_length\",\"confidence_anchor_count\",\"confidence_dom_node_count\",\"finding_id\",\"category\",\"severity\",\"status\",\"weight\",\"title_ro\",\"title_ru\",\"detail_ro\",\"detail_ru\",\"remediation_ro\",\"remediation_ru\",\"legal_reference\",\"legal_classification\",\"legal_applicability\",\"legal_evidence_strength\",\"legal_confidence\",\"legal_regimes\",\"legal_citations\",\"evidence\""
              },
              "application/sarif+json": {
                "schema": {
                  "type": "object",
                  "description": "SARIF 2.1.0 log (OASIS schema)."
                }
              }
            }
          },
          "400": {
            "description": "Malformed request. Error codes: `invalid_request`, `invalid_url`, `unsupported_format`, `invalid_idempotency_key`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key. Never a redirect. Error codes: `unauthorized`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              },
              "WWW-Authenticate": {
                "description": "Bearer challenge (RFC 9110).",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Wrong method on the collection. Error codes: `invalid_request`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The Idempotency-Key was reused with a different body. Error codes: `idempotency_key_conflict`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited, or temporarily blocked after repeated violations. Error codes: `rate_limited`, `temporarily_blocked`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Emitted only when the value is known exactly, which is on rejection: always `0`.",
                "schema": {
                  "type": "integer",
                  "const": 0
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The scan queue is full, or the key store is unavailable. Nothing is wrong with the request. Error codes: `capacity_exceeded`, `auth_unavailable`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/scans/{id}": {
      "get": {
        "tags": [
          "scans"
        ],
        "operationId": "getScan",
        "summary": "Read a scan",
        "description": "Returns the job envelope. While `status` is `pending` or `running`, `result` is null -- poll again. There is no streaming variant in v1.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Response format. `sarif` emits a SARIF 2.1.0 log valid against the OASIS schema; `csv` emits an RFC 4180 document with one row per finding and a stable, append-only header.",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv",
                "sarif"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The scan job.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScanJob"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "\"scan_id\",\"scan_status\",\"requested_url\",\"final_url\",\"scanned_at_utc\",\"verdict\",\"score\",\"confidence_sufficient\",\"confidence_reason\",\"confidence_html_length\",\"confidence_anchor_count\",\"confidence_dom_node_count\",\"finding_id\",\"category\",\"severity\",\"status\",\"weight\",\"title_ro\",\"title_ru\",\"detail_ro\",\"detail_ru\",\"remediation_ro\",\"remediation_ru\",\"legal_reference\",\"legal_classification\",\"legal_applicability\",\"legal_evidence_strength\",\"legal_confidence\",\"legal_regimes\",\"legal_citations\",\"evidence\""
              },
              "application/sarif+json": {
                "schema": {
                  "type": "object",
                  "description": "SARIF 2.1.0 log (OASIS schema)."
                }
              }
            }
          },
          "400": {
            "description": "Unsupported `format`. Error codes: `unsupported_format`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, unknown or revoked API key. Error codes: `unauthorized`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No scan with that id, or it fell outside the retention window. Error codes: `not_found`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The key store is unavailable. Error codes: `auth_unavailable`.",
            "headers": {
              "X-Contract-Version": {
                "description": "Revision of the wire format that produced this response.",
                "schema": {
                  "type": "string",
                  "const": "2026-07-28"
                }
              },
              "X-RateLimit-Limit": {
                "description": "New scans allowed per window, per client IP.",
                "schema": {
                  "type": "integer",
                  "const": 5
                }
              },
              "X-RateLimit-Window": {
                "description": "Length of the sliding window, in seconds.",
                "schema": {
                  "type": "integer",
                  "const": 600
                }
              },
              "X-RateLimit-Policy": {
                "description": "The two above in one field, `limit;w=window`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/openapi": {
      "get": {
        "operationId": "getOpenApiDocument",
        "summary": "This document",
        "description": "The only unauthenticated endpoint in v1.",
        "security": [],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key issued to a lead. Send as `Authorization: Bearer gsm_live_...`. A missing, unknown or revoked key is answered with 401 and a JSON body; this API never redirects an unauthenticated request to a login page."
      }
    },
    "schemas": {
      "Text": {
        "type": "object",
        "description": "Bilingual text. Both languages are always present.",
        "required": [
          "ro",
          "ru"
        ],
        "additionalProperties": false,
        "properties": {
          "ro": {
            "type": "string"
          },
          "ru": {
            "type": "string"
          }
        }
      },
      "Confidence": {
        "type": "object",
        "description": "Rendering evidence behind the verdict. Always present. `sufficient: false` means the page was not seen well enough to audit and no compliance conclusion may be drawn.",
        "required": [
          "sufficient",
          "reason",
          "htmlLength",
          "anchorCount",
          "domNodeCount"
        ],
        "additionalProperties": false,
        "properties": {
          "sufficient": {
            "type": "boolean"
          },
          "reason": {
            "type": "string",
            "description": "Open enumeration. Today: ok, empty_html, too_few_nodes, too_few_anchors. New values may appear.",
            "examples": [
              "ok",
              "too_few_anchors"
            ]
          },
          "htmlLength": {
            "type": "integer",
            "minimum": 0
          },
          "anchorCount": {
            "type": "integer",
            "minimum": 0
          },
          "domNodeCount": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "LegalCitation": {
        "type": "object",
        "required": [
          "jurisdiction",
          "instrument"
        ],
        "additionalProperties": false,
        "properties": {
          "jurisdiction": {
            "type": "string",
            "examples": [
              "md",
              "eu"
            ]
          },
          "instrument": {
            "type": "string",
            "enum": [
              "law",
              "regulation",
              "guidance",
              "decision",
              "other"
            ]
          },
          "number": {
            "type": "string",
            "examples": [
              "195/2024"
            ]
          },
          "year": {
            "type": "integer"
          },
          "article": {
            "type": "string",
            "examples": [
              "29"
            ]
          },
          "paragraph": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "LegalRegime": {
        "type": "object",
        "required": [
          "regime",
          "status",
          "perspective"
        ],
        "additionalProperties": false,
        "properties": {
          "regime": {
            "type": "string",
            "description": "Open enumeration. Today: md-law-133-2011, md-law-195-2024.",
            "examples": [
              "md-law-195-2024"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "enacted",
              "active",
              "superseded",
              "repealed"
            ]
          },
          "perspective": {
            "type": "string",
            "enum": [
              "current",
              "readiness",
              "historical"
            ]
          }
        }
      },
      "Finding": {
        "type": "object",
        "required": [
          "id",
          "category",
          "severity",
          "status",
          "weight",
          "title",
          "detail",
          "remediation",
          "legal",
          "evidence"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable check identifier. Open enumeration -- new checks bring new ids.",
            "examples": [
              "transport.https_forced",
              "policy.not_crawlable"
            ]
          },
          "category": {
            "type": "string",
            "enum": [
              "transport",
              "cookies",
              "cmp",
              "policy",
              "forms",
              "jurisdiction",
              "operator"
            ]
          },
          "severity": {
            "type": "string",
            "enum": [
              "critic",
              "ridicat",
              "mediu",
              "scazut"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pass",
              "fail",
              "warn",
              "na"
            ],
            "description": "`na` = the check does not apply to this site."
          },
          "weight": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "title": {
            "$ref": "#/components/schemas/Text"
          },
          "detail": {
            "$ref": "#/components/schemas/Text"
          },
          "remediation": {
            "$ref": "#/components/schemas/Text"
          },
          "legal": {
            "type": "object",
            "required": [
              "reference",
              "classification",
              "applicability",
              "evidenceStrength",
              "confidence",
              "regimes",
              "citations"
            ],
            "additionalProperties": false,
            "properties": {
              "reference": {
                "type": "string",
                "examples": [
                  "Legea 195/2024 art. 29"
                ]
              },
              "classification": {
                "type": "string",
                "enum": [
                  "observed",
                  "inferred",
                  "needs_context",
                  "not_testable",
                  "error"
                ],
                "description": "Only `observed` carries direct proof of the condition."
              },
              "applicability": {
                "type": "string",
                "enum": [
                  "applicable",
                  "not_applicable",
                  "unknown"
                ]
              },
              "evidenceStrength": {
                "type": "string",
                "enum": [
                  "none",
                  "weak",
                  "moderate",
                  "strong"
                ]
              },
              "confidence": {
                "type": "number",
                "minimum": 0,
                "maximum": 1,
                "description": "Deterministic 0..1 score. Not a probability, not an AI output."
              },
              "regimes": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LegalRegime"
                }
              },
              "citations": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/LegalCitation"
                }
              }
            }
          },
          "evidence": {
            "type": "array",
            "description": "Proof attached to the finding. Server file paths are never included.",
            "items": {
              "type": "object",
              "required": [
                "kind",
                "label",
                "value"
              ],
              "additionalProperties": false,
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "screenshot",
                    "network",
                    "cookie",
                    "header"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Summary": {
        "type": "object",
        "required": [
          "findings",
          "failed",
          "passed",
          "warnings",
          "notApplicable",
          "failedBySeverity"
        ],
        "additionalProperties": false,
        "properties": {
          "findings": {
            "type": "integer",
            "minimum": 0
          },
          "failed": {
            "type": "integer",
            "minimum": 0
          },
          "passed": {
            "type": "integer",
            "minimum": 0
          },
          "warnings": {
            "type": "integer",
            "minimum": 0
          },
          "notApplicable": {
            "type": "integer",
            "minimum": 0
          },
          "failedBySeverity": {
            "type": "object",
            "required": [
              "critic",
              "ridicat",
              "mediu",
              "scazut"
            ],
            "additionalProperties": false,
            "properties": {
              "critic": {
                "type": "integer",
                "minimum": 0
              },
              "ridicat": {
                "type": "integer",
                "minimum": 0
              },
              "mediu": {
                "type": "integer",
                "minimum": 0
              },
              "scazut": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      },
      "ScanResult": {
        "type": "object",
        "required": [
          "contractVersion",
          "scanId",
          "requestedUrl",
          "finalUrl",
          "scannedAt",
          "scannedAtTimezone",
          "verdict",
          "score",
          "confidence",
          "findings",
          "summary"
        ],
        "additionalProperties": false,
        "properties": {
          "contractVersion": {
            "type": "string",
            "const": "2026-07-28"
          },
          "scanId": {
            "type": "string"
          },
          "requestedUrl": {
            "type": "string",
            "format": "uri"
          },
          "finalUrl": {
            "type": "string",
            "format": "uri",
            "description": "Where the browser ended up after redirects."
          },
          "scannedAt": {
            "type": "string",
            "format": "date-time",
            "description": "ISO-8601, always UTC with an explicit Z."
          },
          "scannedAtTimezone": {
            "type": "string",
            "const": "UTC"
          },
          "verdict": {
            "type": "string",
            "enum": [
              "conform",
              "risc",
              "neconform",
              "inconclusiv"
            ],
            "description": "`inconclusiv` means the page did not render well enough to audit. It is NOT a bad result -- it is the absence of one."
          },
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "null when the verdict is `inconclusiv`. Never 0 as a stand-in for \"unknown\"."
          },
          "confidence": {
            "$ref": "#/components/schemas/Confidence"
          },
          "findings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Finding"
            },
            "description": "Empty when the verdict is `inconclusiv`."
          },
          "summary": {
            "$ref": "#/components/schemas/Summary"
          }
        }
      },
      "ScanJob": {
        "type": "object",
        "required": [
          "contractVersion",
          "scanId",
          "status",
          "requestedUrl",
          "createdAt",
          "updatedAt",
          "result",
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "contractVersion": {
            "type": "string",
            "const": "2026-07-28"
          },
          "scanId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "running",
              "done",
              "error"
            ],
            "description": "Lifecycle of the JOB. Distinct from the compliance `verdict` inside `result`."
          },
          "requestedUrl": {
            "type": "string",
            "format": "uri"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "result": {
            "description": "Populated only when `status` is `done`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/ScanResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "error": {
            "description": "Populated only when `status` is `error`. A JOB failure, not an HTTP error.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "code",
                  "message"
                ],
                "additionalProperties": false,
                "properties": {
                  "code": {
                    "type": "string",
                    "const": "scan_failed"
                  },
                  "message": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "contractVersion",
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "contractVersion": {
            "type": "string",
            "const": "2026-07-28"
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "docs"
            ],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "unauthorized",
                  "key_revoked",
                  "invalid_request",
                  "invalid_url",
                  "unsupported_format",
                  "invalid_idempotency_key",
                  "not_found",
                  "idempotency_key_conflict",
                  "rate_limited",
                  "temporarily_blocked",
                  "capacity_exceeded",
                  "auth_unavailable",
                  "internal_error"
                ],
                "description": "Stable, machine-readable. Branch on this, never on `message`."
              },
              "message": {
                "type": "string",
                "description": "English prose for logs. May be reworded at any time."
              },
              "hint": {
                "$ref": "#/components/schemas/Text"
              },
              "retryAfterSeconds": {
                "type": "integer",
                "minimum": 0
              },
              "docs": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  },
  "x-error-codes": {
    "unauthorized": {
      "httpStatus": 401
    },
    "key_revoked": {
      "httpStatus": 401
    },
    "invalid_request": {
      "httpStatus": 400
    },
    "invalid_url": {
      "httpStatus": 400
    },
    "unsupported_format": {
      "httpStatus": 400
    },
    "invalid_idempotency_key": {
      "httpStatus": 400
    },
    "not_found": {
      "httpStatus": 404
    },
    "idempotency_key_conflict": {
      "httpStatus": 409
    },
    "rate_limited": {
      "httpStatus": 429
    },
    "temporarily_blocked": {
      "httpStatus": 429
    },
    "capacity_exceeded": {
      "httpStatus": 503
    },
    "auth_unavailable": {
      "httpStatus": 503
    },
    "internal_error": {
      "httpStatus": 500
    },
    "scan_failed": {
      "httpStatus": 200
    }
  }
}