{
  "components": {
    "schemas": {
      "ApproveBody": {
        "additionalProperties": false,
        "description": "Body for POST /queue/{queue_id}/approve.\n\nSame shape as PatchBody — the approval is conceptually 'commit the\npending edits and finalise'. Separate model so future divergence\n(e.g. an explicit 'final_review_notes' on approve) doesn't leak into\npatch.",
        "properties": {
          "correction_reasons": {
            "additionalProperties": {
              "type": "string"
            },
            "title": "Correction Reasons",
            "type": "object"
          },
          "field_edits": {
            "additionalProperties": true,
            "title": "Field Edits",
            "type": "object"
          }
        },
        "title": "ApproveBody",
        "type": "object"
      },
      "AuthErrorResponse": {
        "additionalProperties": false,
        "description": "Standard auth-error response body.\n\nEvery 4xx from /api/v1/auth/* uses this shape. The frontend branches on\n`error_code` (not message text) so the codes are stable API contract.",
        "properties": {
          "detail": {
            "description": "Error details.",
            "examples": [
              "Email or password is incorrect"
            ],
            "title": "Detail",
            "type": "string"
          },
          "error_code": {
            "description": "Machine-readable error code for client-side branching logic.",
            "examples": [
              "INVALID_CREDENTIALS",
              "RATE_LIMITED"
            ],
            "title": "Error Code",
            "type": "string"
          },
          "retry_after_seconds": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Set only for 429 responses. Seconds to wait before retry.",
            "examples": [
              60
            ],
            "title": "Retry After Seconds"
          }
        },
        "required": [
          "error_code",
          "detail"
        ],
        "title": "AuthErrorResponse",
        "type": "object"
      },
      "BillingCadence": {
        "enum": [
          "monthly",
          "annual",
          "custom"
        ],
        "title": "BillingCadence",
        "type": "string"
      },
      "BillingMode": {
        "enum": [
          "self_pay",
          "org_pay"
        ],
        "title": "BillingMode",
        "type": "string"
      },
      "ComplianceVerdict": {
        "additionalProperties": false,
        "description": "A single compliance verdict for one rule, as returned in JobResponse.\n\nAttributes:\n    verdict_id: UUID of the projects.job_compliance_verdicts row\n    rule_id: SBC rule identifier\n    verdict: PASS / FAIL / NEEDS_REVIEW / OVERRIDDEN\n    confidence: 0.0-1.0; null for deterministic verdicts\n    justification_ar: Arabic justification (regulatory language)\n    justification_en: English justification\n    cited_rule_quote: Verbatim SBC rule text applied\n    cited_drawing_element_id: UUID of the referenced drawing element (nullable)\n    version: Workflow patch version that produced this verdict",
        "properties": {
          "citations": {
            "anyOf": [
              {
                "items": {
                  "additionalProperties": true,
                  "type": "object"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Phase 7 verdict-shape contract: structured citation array (rule quote + source-doc reference) for the workbench's evidence panel. When this field is null/empty the legacy `cited_rule_quote` string is the single citation source.",
            "title": "Citations"
          },
          "cited_drawing_element_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "UUID of the projects.job_drawing_elements row cited as evidence. Null for presence/documentation rules with no specific element.",
            "title": "Cited Drawing Element Id"
          },
          "cited_rule_quote": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Verbatim SBC rule text applied to produce this verdict.",
            "examples": [
              "لا يجب أن يقل عرض الممر عن ١١٢٠ ملم"
            ],
            "title": "Cited Rule Quote"
          },
          "code_edition_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Canonical 'what code edition did we judge against' key. FK to `compliance.code_editions.id`. Pinned for the lifetime of a verdict — if the code is re-ingested as a new edition, old verdicts retain the old id (this is what makes 7y audit-by-replay reproducible).",
            "title": "Code Edition Id"
          },
          "code_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Multi-code platform: the code this verdict was judged against. Joined through `compliance.code_editions.code` from `rules.code_edition_id` for jobs that opted in to multi-code. Null on legacy single-code jobs.",
            "examples": [
              "801",
              "201"
            ],
            "title": "Code Id"
          },
          "confidence": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "LLM confidence score 0.0-1.0. Null for deterministic verdicts (measurement/table_lookup rule types).",
            "examples": [
              0.92
            ],
            "title": "Confidence"
          },
          "element_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Verdict-shape contract field: alias of cited_drawing_element_id for the Phase 7 workbench. Same UUID; new field name lines up with how the CTO frontend groups verdicts by (element_id, code_id).",
            "title": "Element Id"
          },
          "justification_ar": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Arabic justification (regulatory language, RTL).",
            "examples": [
              "عرض الممر 1100 ملم أقل من الحد الأدنى المطلوب 1120 ملم"
            ],
            "title": "Justification Ar"
          },
          "justification_en": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "English justification.",
            "examples": [
              "Corridor width 1100mm is below the required minimum of 1120mm"
            ],
            "title": "Justification En"
          },
          "rule_id": {
            "description": "SBC rule identifier.",
            "examples": [
              "SBC301-1005.1"
            ],
            "title": "Rule Id",
            "type": "string"
          },
          "verdict": {
            "$ref": "#/components/schemas/VerdictValue",
            "description": "The compliance ruling.",
            "examples": [
              "FAIL"
            ]
          },
          "verdict_id": {
            "description": "UUID of the projects.job_compliance_verdicts row.",
            "examples": [
              "a8098c1a-f86e-11da-bd1a-00112444be1e"
            ],
            "format": "uuid",
            "title": "Verdict Id",
            "type": "string"
          },
          "version": {
            "default": 1,
            "description": "Workflow patch version that produced this verdict. Use the row with MAX(version) per rule_id as the current verdict.",
            "examples": [
              1
            ],
            "title": "Version",
            "type": "integer"
          }
        },
        "required": [
          "verdict_id",
          "rule_id",
          "verdict"
        ],
        "title": "ComplianceVerdict",
        "type": "object"
      },
      "CreateCodeEditionRequest": {
        "additionalProperties": false,
        "description": "Body for POST /api/v1/admin/code-editions.",
        "properties": {
          "code_id": {
            "description": "Short identifier for the code (e.g. '801', '201', 'ACME-FIRE').",
            "maxLength": 50,
            "minLength": 1,
            "title": "Code Id",
            "type": "string"
          },
          "code_long_name": {
            "description": "Human-readable name shown in workbench UI + LLM prompts.",
            "maxLength": 400,
            "minLength": 1,
            "title": "Code Long Name",
            "type": "string"
          },
          "edition_year": {
            "description": "Edition year — stored as `edition` varchar on the row.",
            "maximum": 2100.0,
            "minimum": 1900.0,
            "title": "Edition Year",
            "type": "integer"
          },
          "jurisdiction": {
            "description": "ISO-like jurisdiction tag (e.g. 'SA', 'AE').",
            "maxLength": 8,
            "minLength": 1,
            "title": "Jurisdiction",
            "type": "string"
          },
          "language": {
            "description": "BCP-47 tag or composite (e.g. 'en', 'ar', 'ar+en').",
            "maxLength": 8,
            "minLength": 2,
            "title": "Language",
            "type": "string"
          },
          "source_pdfs_count": {
            "description": "How many source PDFs the caller will upload. One presigned PUT URL is returned per slot.",
            "maximum": 20.0,
            "minimum": 1.0,
            "title": "Source Pdfs Count",
            "type": "integer"
          }
        },
        "required": [
          "code_id",
          "code_long_name",
          "edition_year",
          "jurisdiction",
          "language",
          "source_pdfs_count"
        ],
        "title": "CreateCodeEditionRequest",
        "type": "object"
      },
      "CreateSubscriptionRequest": {
        "additionalProperties": false,
        "description": "POST /me/subscription body.",
        "properties": {
          "billing_cadence": {
            "$ref": "#/components/schemas/BillingCadence",
            "description": "'monthly' or 'annual' for Free/Pro; 'custom' only valid on Enterprise (terms set in the contract)."
          },
          "billing_mode": {
            "$ref": "#/components/schemas/BillingMode",
            "description": "'self_pay' charges the user directly; 'org_pay' rolls into the specified organisation's consolidated invoice."
          },
          "payer_organisation_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "REQUIRED when billing_mode='org_pay'.  The organisation whose admin pays the invoice.  Caller must be a member; RLS enforces.",
            "title": "Payer Organisation Id"
          },
          "payg_monthly_ceiling_sar": {
            "anyOf": [
              {
                "minimum": 0.0,
                "type": "number"
              },
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Monthly PAYG ceiling in SAR.  Required when payg_opted_in=true; above this projected charge the next consume_*() call rejects.",
            "title": "Payg Monthly Ceiling Sar"
          },
          "payg_opted_in": {
            "default": false,
            "description": "Opt into PAYG at sign-up.  Only valid when the chosen plan supports PAYG (Pro / Enterprise).",
            "title": "Payg Opted In",
            "type": "boolean"
          },
          "plan_code": {
            "$ref": "#/components/schemas/PlanCode",
            "description": "One of 'free' / 'pro' / 'enterprise'."
          }
        },
        "required": [
          "plan_code",
          "billing_mode",
          "billing_cadence"
        ],
        "title": "CreateSubscriptionRequest",
        "type": "object"
      },
      "CreateWebhookSubscriptionRequest": {
        "additionalProperties": false,
        "description": "POST body.",
        "properties": {
          "display_name": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional human-readable name for the customer dashboard.",
            "title": "Display Name"
          },
          "endpoint_url": {
            "description": "Customer-controlled HTTPS endpoint receiving webhook deliveries.  MUST be HTTPS (CHECK constraint in migration 030 enforces this).",
            "maxLength": 500,
            "pattern": "^https://.+",
            "title": "Endpoint Url",
            "type": "string"
          },
          "event_types": {
            "description": "One or more event types this subscription opts into.",
            "items": {
              "enum": [
                "compliance.job.completed",
                "compliance.job.failed",
                "compliance.verdict.review_required",
                "billing.invoice.cleared",
                "billing.invoice.failed"
              ],
              "type": "string"
            },
            "minItems": 1,
            "title": "Event Types",
            "type": "array"
          },
          "organisation_id": {
            "description": "The organisation this subscription belongs to.  Caller MUST be a member of this organisation (RLS enforces).  At C1's single-firm-per-tenant scale this is usually the user's own org.",
            "format": "uuid",
            "title": "Organisation Id",
            "type": "string"
          },
          "signing_secret": {
            "description": "Customer-generated signing secret used to HMAC-SHA256 the delivered payload.  Min 32 chars (sufficient entropy for HMAC).  Stored HASHED in the DB; the customer keeps the plaintext to verify deliveries.",
            "maxLength": 128,
            "minLength": 32,
            "title": "Signing Secret",
            "type": "string"
          }
        },
        "required": [
          "organisation_id",
          "endpoint_url",
          "signing_secret",
          "event_types"
        ],
        "title": "CreateWebhookSubscriptionRequest",
        "type": "object"
      },
      "DocumentComplete": {
        "properties": {
          "original_filename": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Original Filename",
            "type": "string"
          }
        },
        "required": [
          "original_filename"
        ],
        "title": "DocumentComplete",
        "type": "object"
      },
      "DocumentRegister": {
        "properties": {
          "content_type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Content Type"
          },
          "original_filename": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Original Filename",
            "type": "string"
          }
        },
        "required": [
          "original_filename"
        ],
        "title": "DocumentRegister",
        "type": "object"
      },
      "DocumentResponse": {
        "properties": {
          "document_id": {
            "format": "uuid",
            "title": "Document Id",
            "type": "string"
          },
          "file_hash": {
            "title": "File Hash",
            "type": "string"
          },
          "original_filename": {
            "title": "Original Filename",
            "type": "string"
          },
          "s3_key": {
            "title": "S3 Key",
            "type": "string"
          },
          "size_bytes": {
            "title": "Size Bytes",
            "type": "integer"
          }
        },
        "required": [
          "document_id",
          "original_filename",
          "file_hash",
          "size_bytes",
          "s3_key"
        ],
        "title": "DocumentResponse",
        "type": "object"
      },
      "DocumentUploadResponse": {
        "properties": {
          "document_id": {
            "format": "uuid",
            "title": "Document Id",
            "type": "string"
          },
          "s3_key": {
            "title": "S3 Key",
            "type": "string"
          },
          "upload_url": {
            "title": "Upload Url",
            "type": "string"
          }
        },
        "required": [
          "document_id",
          "upload_url",
          "s3_key"
        ],
        "title": "DocumentUploadResponse",
        "type": "object"
      },
      "DrawingElement": {
        "additionalProperties": false,
        "description": "A visual element extracted from a drawing, as returned in JobResponse.\n\nAttributes:\n    element_id: UUID of the projects.job_drawing_elements row\n    document_id: UUID of the source document\n    element_type: Visual element category\n    label_ar: Arabic label from the drawing (primary)\n    label_en: English label (may be null for Arabic-only drawings)\n    page: 1-indexed page number in the source document\n    bbox: Bounding box in PDF-point coordinates\n    attributes: Measured values and metadata",
        "properties": {
          "attributes": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "Measured values and metadata. Keys vary by element_type. Example for room: {area_sqm: 45.2, width_mm: 4500, height_mm: 3200}.",
            "examples": [
              {
                "area_sqm": 45.2,
                "width_mm": 4500
              }
            ],
            "title": "Attributes"
          },
          "bbox": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "Bounding box in PDF-point coordinates: {x, y, width, height}. Null if bbox was not detected.",
            "examples": [
              {
                "height": 200.0,
                "width": 80.0,
                "x": 120.5,
                "y": 340.2
              }
            ],
            "title": "Bbox"
          },
          "document_id": {
            "description": "UUID of the source project_documents row.",
            "format": "uuid",
            "title": "Document Id",
            "type": "string"
          },
          "element_id": {
            "description": "UUID of the projects.job_drawing_elements row.",
            "format": "uuid",
            "title": "Element Id",
            "type": "string"
          },
          "element_type": {
            "$ref": "#/components/schemas/ElementType",
            "description": "Visual element category.",
            "examples": [
              "room"
            ]
          },
          "label_ar": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Arabic label from the drawing.",
            "examples": [
              "ممر هروب"
            ],
            "title": "Label Ar"
          },
          "label_en": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "English label (may be null for Arabic-only drawings).",
            "examples": [
              "Egress corridor"
            ],
            "title": "Label En"
          },
          "page": {
            "description": "1-indexed page number in the source document.",
            "examples": [
              3
            ],
            "title": "Page",
            "type": "integer"
          }
        },
        "required": [
          "element_id",
          "document_id",
          "element_type",
          "page"
        ],
        "title": "DrawingElement",
        "type": "object"
      },
      "DsarAck": {
        "additionalProperties": false,
        "description": "Response for POST /api/v1/users/me/dsar.",
        "properties": {
          "deadline_at": {
            "description": "PDPL Article 22 30-day resolution deadline (UTC).  The compliance officer must resolve this request before this timestamp or the request transitions to 'expired' status, which triggers a regulatory escalation.",
            "format": "date-time",
            "title": "Deadline At",
            "type": "string"
          },
          "message_ar": {
            "description": "Bilingual acknowledgment in Arabic (regulatory language).",
            "title": "Message Ar",
            "type": "string"
          },
          "message_en": {
            "description": "Bilingual acknowledgment in English.",
            "title": "Message En",
            "type": "string"
          },
          "request_id": {
            "description": "UUID of the users.dsar_requests row that was created.",
            "format": "uuid",
            "title": "Request Id",
            "type": "string"
          },
          "status": {
            "const": "pending",
            "default": "pending",
            "description": "Lifecycle state at intake.  Always 'pending' here; subsequent state transitions ('in_progress', 'completed', 'rejected', 'expired') are visible via the compliance-officer dashboard and via the data subject's request-status endpoint (stub, ships C2).",
            "title": "Status",
            "type": "string"
          }
        },
        "required": [
          "request_id",
          "deadline_at",
          "message_ar",
          "message_en"
        ],
        "title": "DsarAck",
        "type": "object"
      },
      "DsarRequestBody": {
        "additionalProperties": false,
        "description": "Request body for POST /api/v1/users/me/dsar.",
        "properties": {
          "request_details": {
            "anyOf": [
              {
                "maxLength": 4000,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional free-text scope or justification.  Required by some request_types (e.g., 'rectification' must specify what's being corrected).  Validated and stored verbatim for the compliance-officer review queue.",
            "examples": [
              "I would like an export of all personal data associated with my account."
            ],
            "title": "Request Details"
          },
          "request_type": {
            "description": "PDPL Article 17-22 request category.  'access' (Article 17), 'erasure' (Article 19, subject to legal-obligation exemptions for compliance audit records), 'portability' (Article 21), 'rectification' (Article 18), 'objection' (Article 20), 'restriction' (restrict processing pending dispute resolution).",
            "enum": [
              "access",
              "erasure",
              "portability",
              "rectification",
              "objection",
              "restriction"
            ],
            "examples": [
              "access"
            ],
            "title": "Request Type",
            "type": "string"
          }
        },
        "required": [
          "request_type"
        ],
        "title": "DsarRequestBody",
        "type": "object"
      },
      "ElementType": {
        "description": "Persistable drawing-element types — the ``projects.job_drawing_elements.element_type``\nvocabulary.\n\nMirrors the CHECK constraint in ``projects.job_drawing_elements.element_type``\n(kept in lockstep with migration 036 by a parity test). P1 (SP-A) widened this\nto the canonical building-model ``ElementClass`` set so structural rows\n(``wall``/``column``/``beam``/``slab``) and synthesized spatial entities no\nlonger coerce to ``unknown`` and can bind to rules. Two values are NOT\nbuilding-model classes and are kept deliberately distinct:\n\n  - ``occupancy_area`` — a legacy *annotation* element (an AISC ``TOTAL-OCC``\n    marker), NOT the building-model ``occupancy_zone`` grouping.\n  - ``unknown`` — the extractor's fallback when no mapping matches (the\n    building model's escape hatch is ``other``).",
        "enum": [
          "room",
          "door",
          "window",
          "exit",
          "stair",
          "corridor",
          "ramp",
          "shaft",
          "elevator",
          "mezzanine",
          "fire_compartment",
          "occupancy_zone",
          "egress_path",
          "wall",
          "column",
          "beam",
          "slab",
          "foundation",
          "reinforcement",
          "steel_connection",
          "duct",
          "pipe",
          "equipment",
          "building",
          "floor",
          "site",
          "other",
          "occupancy_area",
          "unknown"
        ],
        "title": "ElementType",
        "type": "string"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "title": "HTTPValidationError",
        "type": "object"
      },
      "HealthResponse": {
        "additionalProperties": false,
        "description": "Standard health check response.",
        "properties": {
          "status": {
            "description": "Status: 'alive' for liveness, 'ready' for readiness.",
            "title": "Status",
            "type": "string"
          }
        },
        "required": [
          "status"
        ],
        "title": "HealthResponse",
        "type": "object"
      },
      "JobCreateRequest": {
        "additionalProperties": false,
        "description": "Body for POST /api/v1/jobs — create a new compliance check job.\n\nExamples:\n    {\n      \"project_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"document_ids\": [\"6ba7b810-9dad-11d1-80b4-00c04fd430c8\"],\n      \"query\": \"مشروع مكتب 5 طوابق في الرياض\",\n      \"query_scope\": {\n        \"building_type\": \"office\",\n        \"stories\": 5,\n        \"region\": \"RUH\"\n      }\n    }",
        "properties": {
          "codes": {
            "description": "Multi-code platform input (Phase 5 / Phase 7 contract): the list of design codes to evaluate this job against. Each entry is a `code` identifier matching `compliance.code_editions.code` (e.g. '801' for SBC 801 Fire Code, '201' for SBC 201 General Building). At least one code is required. The caller must be entitled to every code in this list (platform codes are visible to all subscribers; tenant BYO codes are visible only to the owning tenant — see Phase 4). Verdicts in the response will be tagged with `code_id` + `code_edition_id` so the workbench can group them by code.",
            "examples": [
              [
                "801"
              ],
              [
                "801",
                "201"
              ]
            ],
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "title": "Codes",
            "type": "array"
          },
          "confirm_classification": {
            "default": true,
            "description": "When true (default), the workflow PAUSES after scope_classification produces a BuildingProfile draft and waits for the engineer to confirm or edit it via POST /api/v1/jobs/{job_id}/confirm-classification. The licensed Saudi engineer is the source of truth and is legally liable for the verdict, so the AI's classification is always reviewed. Set false ONLY when the engineer has opted into auto-trust mode for this customer (Phase 2+ feature; not active in Phase 0/1).",
            "examples": [
              true
            ],
            "title": "Confirm Classification",
            "type": "boolean"
          },
          "document_ids": {
            "description": "One or more UUIDs of projects.project_documents rows to include in this job. Each document must belong to the given project_id. At least one document is required.",
            "examples": [
              [
                "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
              ]
            ],
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "minItems": 1,
            "title": "Document Ids",
            "type": "array"
          },
          "project_id": {
            "description": "UUID of the projects.projects row this job belongs to.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ],
            "format": "uuid",
            "title": "Project Id",
            "type": "string"
          },
          "query": {
            "description": "Free-text description of the project scope in Arabic or English. Used by ScopeClassificationActivity to determine occupancy class and building type. Example: 'مشروع مكتب 5 طوابق في الرياض، استخدام تجاري مختلط'.",
            "examples": [
              "مشروع مكتب 5 طوابق في الرياض، استخدام تجاري مختلط"
            ],
            "maxLength": 2000,
            "minLength": 10,
            "title": "Query",
            "type": "string"
          },
          "query_scope": {
            "anyOf": [
              {
                "additionalProperties": true,
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional structured scope hints to guide ScopeClassificationActivity. Accepted keys: building_type (str), stories (int), total_area_sqm (float). All keys are optional; the LLM infers from the query when not provided. NOTE: For region, prefer the explicit `region` field below — it's machine-checkable and feeds directly into the wind-load + temperature rule applicability filters.",
            "examples": [
              {
                "building_type": "office",
                "stories": 5
              }
            ],
            "title": "Query Scope"
          },
          "region": {
            "anyOf": [
              {
                "pattern": "^[A-Z]{3}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Optional KSA region code where the building is located. When set, scope_classification trusts this value and skips region inference from drawings. Wind-load + temperature/humidity rules in SBC are region-specific so explicit input reduces classification risk. Accepted: RUH (Riyadh), JED (Jeddah), DMM (Dammam), MEC (Makkah), MED (Madinah), TUU (Tabuk), AHB (Abha/Asir). Other ISO codes accepted but trigger a warning.",
            "examples": [
              "RUH",
              "JED"
            ],
            "title": "Region"
          }
        },
        "required": [
          "project_id",
          "document_ids",
          "query",
          "codes"
        ],
        "title": "JobCreateRequest",
        "type": "object"
      },
      "JobResponse": {
        "additionalProperties": false,
        "description": "Response for GET /api/v1/jobs/{job_id} and POST /api/v1/jobs.\n\nThe response shape is the same for both endpoints. On POST, status='pending'\nand all verdict/element fields are null. On GET after completion,\nthe full verdict + element sets are populated.\n\nExamples:\n    # POST /jobs response (pending)\n    {\n      \"job_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"status\": \"pending\",\n      \"estimated_cost_credits\": 12.5,\n      \"eta_seconds\": 90\n    }\n\n    # GET /jobs/{id} response (completed)\n    {\n      \"job_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n      \"status\": \"completed\",\n      \"verdict_summary\": {\"total\": 34, \"pass_count\": 28, ...},\n      \"verdicts\": [...],\n      \"elements\": [...]\n    }",
        "properties": {
          "elements": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/DrawingElement"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Visual elements extracted from the drawings. Null until job status='completed'.",
            "title": "Elements"
          },
          "error_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Machine-readable error code. Only present when status='failed'.",
            "examples": [
              "DOCUMENT_PARSE_FAILED"
            ],
            "title": "Error Code"
          },
          "error_message_ar": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Arabic error message. Only present when status='failed'.",
            "examples": [
              "فشل تحليل ملف PDF: تنسيق غير مدعوم"
            ],
            "title": "Error Message Ar"
          },
          "error_message_en": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "English error message. Only present when status='failed'.",
            "examples": [
              "Document parse failed: unsupported format"
            ],
            "title": "Error Message En"
          },
          "estimated_cost_credits": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Estimated credit cost for this job. Set at job creation based on document count and rule set size. Null until scope classification completes.",
            "examples": [
              12.5
            ],
            "title": "Estimated Cost Credits"
          },
          "eta_seconds": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Estimated seconds until completion. Null for completed/failed/cancelled jobs.",
            "examples": [
              90
            ],
            "title": "Eta Seconds"
          },
          "job_id": {
            "description": "UUID of the compliance job.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ],
            "format": "uuid",
            "title": "Job Id",
            "type": "string"
          },
          "result_pdf_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "S3 presigned URL to download the verdict PDF. Valid for 15 minutes after generation. Null until job status='completed'. Use GET /api/v1/jobs/{job_id}/result.pdf to get a fresh URL.",
            "examples": [
              "https://s3.eu-central-1.amazonaws.com/emtithal-...?X-Amz-Expires=900"
            ],
            "title": "Result Pdf Url"
          },
          "status": {
            "$ref": "#/components/schemas/JobStatus",
            "description": "Current lifecycle status of the job.",
            "examples": [
              "pending"
            ]
          },
          "verdict_summary": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/VerdictSummary"
              },
              {
                "type": "null"
              }
            ],
            "description": "Aggregate verdict counts. Null until job status='completed'."
          },
          "verdicts": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/ComplianceVerdict"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Per-rule compliance verdicts. Null until job status='completed'. Each entry is the latest-version verdict per rule_id.",
            "title": "Verdicts"
          },
          "verdicts_inline_count": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Number of verdict rows in the inline `verdicts` slice. Present only when `verdicts_truncated` is True.",
            "title": "Verdicts Inline Count"
          },
          "verdicts_truncated": {
            "default": false,
            "description": "True when the inline `verdicts` list is a bounded slice of the full result set. Use `verdicts_url` to retrieve all verdicts via pagination.",
            "title": "Verdicts Truncated",
            "type": "boolean"
          },
          "verdicts_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "URL to the paginated verdict list for this job (GET /api/v1/jobs/{job_id}/verdicts). Present when status='completed'. Use this endpoint rather than the inline `verdicts` field for large jobs.",
            "examples": [
              "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000/verdicts"
            ],
            "title": "Verdicts Url"
          }
        },
        "required": [
          "job_id",
          "status"
        ],
        "title": "JobResponse",
        "type": "object"
      },
      "JobStatus": {
        "description": "Lifecycle status of a compliance job.\n\nMirrors the CHECK constraint in projects.jobs.status.",
        "enum": [
          "pending",
          "running",
          "completed",
          "failed",
          "cancelled",
          "expired"
        ],
        "title": "JobStatus",
        "type": "string"
      },
      "LoginRequest": {
        "additionalProperties": false,
        "properties": {
          "email": {
            "description": "Registered email address.",
            "examples": [
              "user@example.com"
            ],
            "format": "email",
            "title": "Email",
            "type": "string"
          },
          "password": {
            "description": "Account password.",
            "examples": [
              "MySecureP@ss123"
            ],
            "maxLength": 128,
            "minLength": 1,
            "title": "Password",
            "type": "string"
          }
        },
        "required": [
          "email",
          "password"
        ],
        "title": "LoginRequest",
        "type": "object"
      },
      "OverrideRequest": {
        "additionalProperties": false,
        "description": "Body for POST /api/v1/jobs/{job_id}/override — reviewer verdict override.\n\nOverride is a first-class audit event. Justification is required; there are\nno rubber-stamp overrides. The licensed engineer must provide the reason in\nthe regulatory language (Arabic preferred; English accepted).\n\nExamples:\n    {\n      \"rule_id\": \"SBC301-1005.1\",\n      \"original_verdict\": \"FAIL\",\n      \"new_verdict\": \"PASS\",\n      \"justification\": \"المسافة المقيسة على الرسم صحيحة؛ الخطأ في التعرف البصري للحدود\"\n    }",
        "properties": {
          "justification": {
            "description": "Required free-text justification for the override. Arabic is preferred (regulatory language). Minimum 20 characters. This text is preserved in projects.job_overrides for 7-year audit retention.",
            "examples": [
              "المسافة المقيسة على الرسم صحيحة؛ الخطأ في التعرف البصري للحدود"
            ],
            "maxLength": 2000,
            "minLength": 20,
            "title": "Justification",
            "type": "string"
          },
          "new_verdict": {
            "$ref": "#/components/schemas/VerdictValue",
            "description": "The reviewer's replacement verdict. OVERRIDDEN is set automatically by the system; supply PASS, FAIL, or NEEDS_REVIEW.",
            "examples": [
              "PASS"
            ]
          },
          "original_verdict": {
            "$ref": "#/components/schemas/VerdictValue",
            "description": "The verdict being replaced. Must match the current verdict in the DB.",
            "examples": [
              "FAIL"
            ]
          },
          "rule_id": {
            "description": "Rule identifier whose verdict is being overridden. Must match a rule_id in compliance.rules. Accepts live corpus IDs such as 'SBC-801-303.7-b77eaf76' (slash-separated taxonomy paths) and future edition-qualified IDs of the form '{code}:{edition}:{section}'.",
            "examples": [
              "SBC-801-303.7-b77eaf76"
            ],
            "pattern": "^[A-Za-z0-9._/:\\-]{1,120}$",
            "title": "Rule Id",
            "type": "string"
          }
        },
        "required": [
          "rule_id",
          "original_verdict",
          "new_verdict",
          "justification"
        ],
        "title": "OverrideRequest",
        "type": "object"
      },
      "PatchBody": {
        "additionalProperties": false,
        "description": "Body for PATCH /queue/{queue_id} — incremental reviewer field edits.\n\n`field_edits` is intentionally `dict[str, Any]` because the SBC RuleData\nschema is heterogeneous (numeric thresholds, Arabic strings, structured\ncitations). The forbid-extra at the top level prevents stray top-level\nkeys; the per-field shape is enforced downstream by the JSON-schema\nvalidation in the rule-db pipeline (out of route-layer scope).",
        "properties": {
          "correction_reasons": {
            "additionalProperties": {
              "type": "string"
            },
            "title": "Correction Reasons",
            "type": "object"
          },
          "field_edits": {
            "additionalProperties": true,
            "title": "Field Edits",
            "type": "object"
          }
        },
        "title": "PatchBody",
        "type": "object"
      },
      "PlanCode": {
        "enum": [
          "free",
          "pro",
          "enterprise"
        ],
        "title": "PlanCode",
        "type": "string"
      },
      "PlanResponse": {
        "additionalProperties": false,
        "description": "A row from billing.plans, transmitted over the wire.",
        "properties": {
          "annual_price_sar": {
            "anyOf": [
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Annual Price Sar"
          },
          "audit_retention_days": {
            "title": "Audit Retention Days",
            "type": "integer"
          },
          "billing_cadence": {
            "title": "Billing Cadence",
            "type": "string"
          },
          "code": {
            "title": "Code",
            "type": "string"
          },
          "compliance_review_payg_unit_price_sar": {
            "anyOf": [
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "NULL on hard-cap plans (Free)",
            "title": "Compliance Review Payg Unit Price Sar"
          },
          "compliance_reviews_per_month": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "NULL = unlimited (Enterprise)",
            "title": "Compliance Reviews Per Month"
          },
          "display_name": {
            "title": "Display Name",
            "type": "string"
          },
          "generative_token_payg_unit_price_sar_per_1k": {
            "anyOf": [
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Generative Token Payg Unit Price Sar Per 1K"
          },
          "includes_cultural_overlay": {
            "title": "Includes Cultural Overlay",
            "type": "boolean"
          },
          "max_webhook_deliveries_per_month": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Max Webhook Deliveries Per Month"
          },
          "max_webhook_subscriptions": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Max Webhook Subscriptions"
          },
          "min_commitment_months": {
            "title": "Min Commitment Months",
            "type": "integer"
          },
          "monthly_price_sar": {
            "anyOf": [
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Monthly Price Sar"
          },
          "payg_default_ceiling_sar": {
            "anyOf": [
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Payg Default Ceiling Sar"
          },
          "pdf_branding": {
            "title": "Pdf Branding",
            "type": "string"
          },
          "storage_gb_included": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Storage Gb Included"
          },
          "storage_payg_unit_price_sar": {
            "anyOf": [
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Storage Payg Unit Price Sar"
          },
          "supports_org_pay": {
            "title": "Supports Org Pay",
            "type": "boolean"
          },
          "supports_payg": {
            "title": "Supports Payg",
            "type": "boolean"
          },
          "supports_self_pay": {
            "title": "Supports Self Pay",
            "type": "boolean"
          }
        },
        "required": [
          "code",
          "display_name",
          "monthly_price_sar",
          "annual_price_sar",
          "billing_cadence",
          "storage_gb_included",
          "storage_payg_unit_price_sar",
          "audit_retention_days",
          "max_webhook_subscriptions",
          "max_webhook_deliveries_per_month",
          "includes_cultural_overlay",
          "pdf_branding",
          "generative_token_payg_unit_price_sar_per_1k",
          "supports_payg",
          "payg_default_ceiling_sar",
          "min_commitment_months",
          "supports_self_pay",
          "supports_org_pay"
        ],
        "title": "PlanResponse",
        "type": "object"
      },
      "ProjectCreate": {
        "properties": {
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "title": "ProjectCreate",
        "type": "object"
      },
      "ProjectResponse": {
        "properties": {
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "name": {
            "title": "Name",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "created_at"
        ],
        "title": "ProjectResponse",
        "type": "object"
      },
      "QACitation": {
        "additionalProperties": false,
        "description": "A single cited rule returned alongside a Q&A answer.\n\n`relevance_score` is `1.0 - cosine_distance` so 1.0 = identical and 0.0 =\northogonal. The retriever computes it from pgvector's `<=>` cosine-distance\noperator — see ``retriever.RETRIEVE_TOP_K_SQL`` for the live query.",
        "properties": {
          "code_edition_id": {
            "description": "UUID of the code_edition this rule was extracted from.",
            "format": "uuid",
            "title": "Code Edition Id",
            "type": "string"
          },
          "recovered": {
            "default": false,
            "description": "AG-C1: True if this citation was RECOVERED by a rule_id lookup because the LLM cited a real rule that was not in the retrieval top-K (vs retrieved by similarity). Distinguishes recovered-by-lookup from retrieved so the UI can label provenance.",
            "title": "Recovered",
            "type": "boolean"
          },
          "relevance_score": {
            "description": "1.0 - pgvector cosine distance (`<=>` operator). Higher = more similar. Range intentionally NOT clamped so out-of-band values surface embedding-shape mismatches loudly. Not meaningful (0.0) for recovered citations — see `recovered`.",
            "title": "Relevance Score",
            "type": "number"
          },
          "rule_id": {
            "description": "Human-readable rule id (e.g. '801-7.4.2').",
            "title": "Rule Id",
            "type": "string"
          },
          "rule_text_ar": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Arabic rule body (NULL for EN-only sources).",
            "title": "Rule Text Ar"
          },
          "rule_text_en": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "English rule body (NULL for AR-only sources).",
            "title": "Rule Text En"
          },
          "section_number": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Section number within the code (e.g. '7.4.2'). May be None for rules ingested via a path that didn't preserve a structured section number — surfaced from `compliance.rules.taxonomy_path`.",
            "title": "Section Number"
          },
          "source_pdf_s3_key": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "S3 key of the source PDF for click-through linking. NULL while the ingestion pipeline hasn't yet wired source_doc back through rule_sources — Phase 2 integration point.",
            "title": "Source Pdf S3 Key"
          }
        },
        "required": [
          "rule_id",
          "code_edition_id",
          "relevance_score"
        ],
        "title": "QACitation",
        "type": "object"
      },
      "QARequestBody": {
        "additionalProperties": false,
        "description": "POST body for /api/v1/codes/{code_id}/qa.\n\n`code_id` is in the URL path; only the question + lang + top_k go in the\nbody. We re-validate via `QAQuestion` server-side so the type contract\nin `emtithal_rule_db.qa.types` is the canonical shape.",
        "properties": {
          "lang": {
            "default": "ar",
            "title": "Lang",
            "type": "string"
          },
          "question_text": {
            "maxLength": 2000,
            "minLength": 1,
            "title": "Question Text",
            "type": "string"
          },
          "top_k": {
            "default": 10,
            "maximum": 50.0,
            "minimum": 1.0,
            "title": "Top K",
            "type": "integer"
          }
        },
        "required": [
          "question_text"
        ],
        "title": "QARequestBody",
        "type": "object"
      },
      "QAResponse": {
        "additionalProperties": false,
        "description": "Response from the synthesizer — the answer + the citations used.\n\n`citations` is the subset that the LLM actually grounded its answer in,\nNOT the full top-K returned by the retriever. The route layer keeps the\nfull top-K available for audit purposes (`citation_count` in the audit\nevent = len(citations), `retrieved_count` = top_k).",
        "properties": {
          "answer_text": {
            "description": "Final answer in the requested language.",
            "title": "Answer Text",
            "type": "string"
          },
          "citations": {
            "description": "Rules the LLM grounded its answer in.",
            "items": {
              "$ref": "#/components/schemas/QACitation"
            },
            "title": "Citations",
            "type": "array"
          },
          "lang": {
            "description": "Language of `answer_text` (echoes request.lang).",
            "enum": [
              "ar",
              "en"
            ],
            "title": "Lang",
            "type": "string"
          },
          "model_version": {
            "description": "LLM model_version used for synthesis (e.g. 'Qwen3-30B-A3B-Instruct-2507-FP8').",
            "title": "Model Version",
            "type": "string"
          },
          "prompt_hash": {
            "description": "sha256:<hex> of the rendered prompt — replay-by-hash anchor.",
            "title": "Prompt Hash",
            "type": "string"
          },
          "prompt_version": {
            "description": "Version string of the prompt template (e.g. 'system_qa_ar.v1').",
            "title": "Prompt Version",
            "type": "string"
          }
        },
        "required": [
          "answer_text",
          "lang",
          "model_version",
          "prompt_hash",
          "prompt_version"
        ],
        "title": "QAResponse",
        "type": "object"
      },
      "RegisterRequest": {
        "additionalProperties": false,
        "properties": {
          "email": {
            "description": "Email address for the new account. Must be unique.",
            "examples": [
              "user@example.com"
            ],
            "format": "email",
            "title": "Email",
            "type": "string"
          },
          "eula_sha256": {
            "description": "SHA-256 (64 hex chars) of the exact EULA document the user accepted.",
            "examples": [
              "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
            ],
            "pattern": "^[a-fA-F0-9]{64}$",
            "title": "Eula Sha256",
            "type": "string"
          },
          "eula_version": {
            "description": "Version identifier of the EULA the user accepted.",
            "examples": [
              "2026-06-01"
            ],
            "maxLength": 20,
            "title": "Eula Version",
            "type": "string"
          },
          "password": {
            "description": "Password (12-128 chars). Must use 3 of 4 character classes (lowercase, uppercase, digit, special) or be 16+ characters long.",
            "examples": [
              "MySecureP@ss123"
            ],
            "maxLength": 128,
            "minLength": 12,
            "title": "Password",
            "type": "string"
          },
          "preferred_lang": {
            "default": "en",
            "description": "Preferred language (ISO 639-1 code). Defaults to 'en'. Used for error messages and email templates.",
            "examples": [
              "en",
              "ar"
            ],
            "maxLength": 10,
            "minLength": 2,
            "title": "Preferred Lang",
            "type": "string"
          }
        },
        "required": [
          "email",
          "password",
          "eula_version",
          "eula_sha256"
        ],
        "title": "RegisterRequest",
        "type": "object"
      },
      "RegisterResponse": {
        "additionalProperties": false,
        "properties": {
          "message": {
            "default": "Verification email sent. Check your inbox.",
            "description": "User-facing message indicating next step (email verification).",
            "title": "Message",
            "type": "string"
          },
          "user_id": {
            "description": "UUID of the new user account.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ],
            "format": "uuid",
            "title": "User Id",
            "type": "string"
          }
        },
        "required": [
          "user_id"
        ],
        "title": "RegisterResponse",
        "type": "object"
      },
      "RejectBody": {
        "additionalProperties": false,
        "description": "Body for POST /queue/{queue_id}/reject. Reason is required.",
        "properties": {
          "reason": {
            "title": "Reason",
            "type": "string"
          }
        },
        "required": [
          "reason"
        ],
        "title": "RejectBody",
        "type": "object"
      },
      "RequestPasswordResetRequest": {
        "additionalProperties": false,
        "properties": {
          "email": {
            "description": "Email address of the account to reset. The response is deliberately identical whether or not this email is registered (no account-existence disclosure).",
            "examples": [
              "user@example.com"
            ],
            "format": "email",
            "title": "Email",
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "title": "RequestPasswordResetRequest",
        "type": "object"
      },
      "RequestPasswordResetResponse": {
        "additionalProperties": false,
        "properties": {
          "message": {
            "default": "If that email is registered, a reset code has been sent.",
            "description": "Generic, non-enumerable confirmation. Always returned regardless of whether the email exists.",
            "title": "Message",
            "type": "string"
          }
        },
        "title": "RequestPasswordResetResponse",
        "type": "object"
      },
      "ResetPasswordRequest": {
        "additionalProperties": false,
        "properties": {
          "email": {
            "description": "Email address the reset code was requested for.",
            "examples": [
              "user@example.com"
            ],
            "format": "email",
            "title": "Email",
            "type": "string"
          },
          "new_password": {
            "description": "New password (12-128 chars). Must use 3 of 4 character classes (lowercase, uppercase, digit, special) or be 16+ characters.",
            "examples": [
              "MyNewSecureP@ss123"
            ],
            "maxLength": 128,
            "minLength": 12,
            "title": "New Password",
            "type": "string"
          },
          "otp": {
            "description": "6-digit reset code sent to the email.",
            "examples": [
              "123456"
            ],
            "maxLength": 6,
            "minLength": 6,
            "pattern": "^\\d{6}$",
            "title": "Otp",
            "type": "string"
          }
        },
        "required": [
          "email",
          "otp",
          "new_password"
        ],
        "title": "ResetPasswordRequest",
        "type": "object"
      },
      "ResetPasswordResponse": {
        "additionalProperties": false,
        "properties": {
          "message": {
            "default": "Password reset. All existing sessions were signed out; please log in with your new password.",
            "description": "Confirmation that the password changed and all refresh tokens were revoked. The user must log in again.",
            "title": "Message",
            "type": "string"
          }
        },
        "title": "ResetPasswordResponse",
        "type": "object"
      },
      "ServiceUnavailableError": {
        "additionalProperties": false,
        "description": "AUD-060: wire shape for /readyz 503 responses.\n\nFastAPI's default HTTPException handler serialises\n``HTTPException(status_code=503, detail=\"db unavailable\")`` as\n``{\"detail\": \"db unavailable\"}`` — no ``error_code`` field.\nThis model matches the actual wire payload.",
        "properties": {
          "detail": {
            "description": "Human-readable error detail: 'db unavailable' or 'redis unavailable'.",
            "title": "Detail",
            "type": "string"
          }
        },
        "required": [
          "detail"
        ],
        "title": "ServiceUnavailableError",
        "type": "object"
      },
      "SubscriptionResponse": {
        "additionalProperties": false,
        "properties": {
          "billing_cadence": {
            "title": "Billing Cadence",
            "type": "string"
          },
          "billing_mode": {
            "title": "Billing Mode",
            "type": "string"
          },
          "cancel_at_period_end": {
            "title": "Cancel At Period End",
            "type": "boolean"
          },
          "commitment_end_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Commitment End At"
          },
          "current_period_end": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Current Period End"
          },
          "current_period_start": {
            "format": "date-time",
            "title": "Current Period Start",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "payer_organisation_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Payer Organisation Id"
          },
          "payer_user_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Payer User Id"
          },
          "payg_monthly_ceiling_sar": {
            "anyOf": [
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Payg Monthly Ceiling Sar"
          },
          "payg_opted_in": {
            "title": "Payg Opted In",
            "type": "boolean"
          },
          "plan_code": {
            "title": "Plan Code",
            "type": "string"
          },
          "status": {
            "title": "Status",
            "type": "string"
          },
          "user_id": {
            "format": "uuid",
            "title": "User Id",
            "type": "string"
          }
        },
        "required": [
          "id",
          "user_id",
          "plan_code",
          "billing_mode",
          "payer_user_id",
          "payer_organisation_id",
          "billing_cadence",
          "status",
          "current_period_start",
          "current_period_end",
          "commitment_end_at",
          "cancel_at_period_end",
          "payg_opted_in",
          "payg_monthly_ceiling_sar"
        ],
        "title": "SubscriptionResponse",
        "type": "object"
      },
      "TestPingResult": {
        "additionalProperties": false,
        "description": "Response for POST /{id}/test.",
        "properties": {
          "delivered": {
            "description": "True if the customer endpoint returned 2xx; False otherwise.",
            "title": "Delivered",
            "type": "boolean"
          },
          "error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Brief error message if delivered=False.",
            "title": "Error"
          },
          "http_status": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "HTTP status code returned by the customer endpoint.  None if the request failed before getting a response (DNS / connect / TLS / timeout).",
            "title": "Http Status"
          },
          "latency_ms": {
            "description": "Wall-clock milliseconds between request send + response receive (or timeout).",
            "title": "Latency Ms",
            "type": "integer"
          }
        },
        "required": [
          "delivered",
          "latency_ms"
        ],
        "title": "TestPingResult",
        "type": "object"
      },
      "TokenResponse": {
        "additionalProperties": false,
        "description": "Returned by login, verify-email, refresh — the full session envelope.",
        "properties": {
          "access_token": {
            "description": "JWT access token. Valid for ~1 hour. Include in Authorization: Bearer header.",
            "examples": [
              "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
            ],
            "title": "Access Token",
            "type": "string"
          },
          "email": {
            "description": "Authenticated user's email address.",
            "examples": [
              "user@example.com"
            ],
            "title": "Email",
            "type": "string"
          },
          "expires_in": {
            "description": "Access token TTL in seconds from issue time.",
            "examples": [
              3600
            ],
            "title": "Expires In",
            "type": "integer"
          },
          "tier": {
            "description": "Subscription tier.",
            "examples": [
              "free",
              "professional",
              "enterprise"
            ],
            "title": "Tier",
            "type": "string"
          },
          "token_type": {
            "default": "Bearer",
            "description": "Token type (always 'Bearer' for JWT).",
            "examples": [
              "Bearer"
            ],
            "title": "Token Type",
            "type": "string"
          },
          "user_id": {
            "description": "Authenticated user's UUID.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ],
            "format": "uuid",
            "title": "User Id",
            "type": "string"
          }
        },
        "required": [
          "access_token",
          "expires_in",
          "user_id",
          "email",
          "tier"
        ],
        "title": "TokenResponse",
        "type": "object"
      },
      "UpdatePaygRequest": {
        "additionalProperties": false,
        "description": "PATCH /me/subscription/payg body.",
        "properties": {
          "payg_monthly_ceiling_sar": {
            "anyOf": [
              {
                "minimum": 0.0,
                "type": "number"
              },
              {
                "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Payg Monthly Ceiling Sar"
          },
          "payg_opted_in": {
            "title": "Payg Opted In",
            "type": "boolean"
          }
        },
        "required": [
          "payg_opted_in"
        ],
        "title": "UpdatePaygRequest",
        "type": "object"
      },
      "UsageMeterResponse": {
        "additionalProperties": false,
        "properties": {
          "compliance_reviews_payg_count": {
            "title": "Compliance Reviews Payg Count",
            "type": "integer"
          },
          "compliance_reviews_used": {
            "title": "Compliance Reviews Used",
            "type": "integer"
          },
          "generative_tokens_payg": {
            "title": "Generative Tokens Payg",
            "type": "integer"
          },
          "generative_tokens_used": {
            "title": "Generative Tokens Used",
            "type": "integer"
          },
          "payg_charges_sar": {
            "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
            "title": "Payg Charges Sar",
            "type": "string"
          },
          "period_end": {
            "format": "date-time",
            "title": "Period End",
            "type": "string"
          },
          "period_start": {
            "format": "date-time",
            "title": "Period Start",
            "type": "string"
          },
          "storage_gb_payg": {
            "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
            "title": "Storage Gb Payg",
            "type": "string"
          },
          "storage_gb_used": {
            "pattern": "^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$",
            "title": "Storage Gb Used",
            "type": "string"
          },
          "webhook_deliveries_payg": {
            "title": "Webhook Deliveries Payg",
            "type": "integer"
          },
          "webhook_deliveries_used": {
            "title": "Webhook Deliveries Used",
            "type": "integer"
          }
        },
        "required": [
          "period_start",
          "period_end",
          "compliance_reviews_used",
          "compliance_reviews_payg_count",
          "storage_gb_used",
          "storage_gb_payg",
          "webhook_deliveries_used",
          "webhook_deliveries_payg",
          "generative_tokens_used",
          "generative_tokens_payg",
          "payg_charges_sar"
        ],
        "title": "UsageMeterResponse",
        "type": "object"
      },
      "UserMeResponse": {
        "additionalProperties": false,
        "description": "GET /api/v1/auth/me — the current user's profile.",
        "properties": {
          "email": {
            "examples": [
              "user@example.com"
            ],
            "title": "Email",
            "type": "string"
          },
          "has_verified_email": {
            "description": "Whether email is verified",
            "examples": [
              true
            ],
            "title": "Has Verified Email",
            "type": "boolean"
          },
          "preferred_lang": {
            "description": "ISO 639-1 language code",
            "examples": [
              "en",
              "ar"
            ],
            "title": "Preferred Lang",
            "type": "string"
          },
          "status": {
            "description": "Account status",
            "examples": [
              "active",
              "suspended"
            ],
            "title": "Status",
            "type": "string"
          },
          "subscription_tier": {
            "description": "Current subscription tier",
            "examples": [
              "free"
            ],
            "title": "Subscription Tier",
            "type": "string"
          },
          "user_id": {
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ],
            "format": "uuid",
            "title": "User Id",
            "type": "string"
          }
        },
        "required": [
          "user_id",
          "email",
          "preferred_lang",
          "subscription_tier",
          "status",
          "has_verified_email"
        ],
        "title": "UserMeResponse",
        "type": "object"
      },
      "ValidationError": {
        "properties": {
          "ctx": {
            "title": "Context",
            "type": "object"
          },
          "input": {
            "title": "Input"
          },
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "title": "Location",
            "type": "array"
          },
          "msg": {
            "title": "Message",
            "type": "string"
          },
          "type": {
            "title": "Error Type",
            "type": "string"
          }
        },
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError",
        "type": "object"
      },
      "VerdictSummary": {
        "additionalProperties": false,
        "description": "Aggregate verdict counts for a completed job.",
        "properties": {
          "fail_count": {
            "description": "Count of FAIL verdicts.",
            "examples": [
              4
            ],
            "title": "Fail Count",
            "type": "integer"
          },
          "needs_review_count": {
            "description": "Count of NEEDS_REVIEW verdicts.",
            "examples": [
              2
            ],
            "title": "Needs Review Count",
            "type": "integer"
          },
          "not_applicable_count": {
            "default": 0,
            "description": "Count of NOT_APPLICABLE verdicts (rules deterministically filtered out because the project lacks the elements they govern).",
            "examples": [
              0
            ],
            "title": "Not Applicable Count",
            "type": "integer"
          },
          "overridden_count": {
            "default": 0,
            "description": "Count of OVERRIDDEN verdicts (reviewer overrides).",
            "examples": [
              0
            ],
            "title": "Overridden Count",
            "type": "integer"
          },
          "pass_count": {
            "description": "Count of PASS verdicts.",
            "examples": [
              28
            ],
            "title": "Pass Count",
            "type": "integer"
          },
          "total": {
            "description": "Total number of verdicts evaluated.",
            "examples": [
              34
            ],
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "total",
          "pass_count",
          "fail_count",
          "needs_review_count"
        ],
        "title": "VerdictSummary",
        "type": "object"
      },
      "VerdictValue": {
        "description": "Possible verdicts for a compliance evaluation.\n\nMirrors the CHECK constraint in projects.job_compliance_verdicts.verdict.",
        "enum": [
          "PASS",
          "FAIL",
          "NEEDS_REVIEW",
          "OVERRIDDEN",
          "NOT_APPLICABLE"
        ],
        "title": "VerdictValue",
        "type": "string"
      },
      "VerdictsFilters": {
        "additionalProperties": true,
        "description": "AUD-061: filters applied to the verdict list query.",
        "properties": {
          "code_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Code Id"
          },
          "element_type": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Element Type"
          },
          "limit": {
            "default": 0,
            "title": "Limit",
            "type": "integer"
          },
          "offset": {
            "default": 0,
            "title": "Offset",
            "type": "integer"
          },
          "verdict_state": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Verdict State"
          }
        },
        "title": "VerdictsFilters",
        "type": "object"
      },
      "VerdictsListResponse": {
        "additionalProperties": false,
        "description": "AUD-061: typed response for GET /jobs/{job_id}/verdicts.\n\n``verdicts`` is a list of raw DB rows (each is a dict keyed per\ndocs/api/verdict-shape.md).  Using ``list[Any]`` avoids coupling the route\nlayer to a DB-shape Pydantic model that would need to track every column\nrename — the CTO can generate types from the committed openapi.json schema.",
        "properties": {
          "filters": {
            "$ref": "#/components/schemas/VerdictsFilters"
          },
          "job_id": {
            "format": "uuid",
            "title": "Job Id",
            "type": "string"
          },
          "total_count": {
            "title": "Total Count",
            "type": "integer"
          },
          "verdicts": {
            "items": {},
            "title": "Verdicts",
            "type": "array"
          }
        },
        "required": [
          "job_id",
          "verdicts",
          "total_count",
          "filters"
        ],
        "title": "VerdictsListResponse",
        "type": "object"
      },
      "VerifyEmailRequest": {
        "additionalProperties": false,
        "properties": {
          "otp": {
            "description": "6-digit OTP sent to the registered email address.",
            "examples": [
              "123456"
            ],
            "maxLength": 6,
            "minLength": 6,
            "pattern": "^\\d{6}$",
            "title": "Otp",
            "type": "string"
          },
          "user_id": {
            "description": "UUID from the register response.",
            "examples": [
              "550e8400-e29b-41d4-a716-446655440000"
            ],
            "format": "uuid",
            "title": "User Id",
            "type": "string"
          }
        },
        "required": [
          "user_id",
          "otp"
        ],
        "title": "VerifyEmailRequest",
        "type": "object"
      },
      "WebhookSubscriptionResponse": {
        "additionalProperties": false,
        "description": "Single-row response.  signing_secret is NEVER returned -- only the\ncustomer keeps the plaintext.  We only confirm storage.",
        "properties": {
          "display_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Display Name"
          },
          "endpoint_url": {
            "title": "Endpoint Url",
            "type": "string"
          },
          "event_types": {
            "items": {
              "type": "string"
            },
            "title": "Event Types",
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "is_active": {
            "title": "Is Active",
            "type": "boolean"
          },
          "organisation_id": {
            "format": "uuid",
            "title": "Organisation Id",
            "type": "string"
          }
        },
        "required": [
          "id",
          "organisation_id",
          "endpoint_url",
          "event_types",
          "is_active"
        ],
        "title": "WebhookSubscriptionResponse",
        "type": "object"
      }
    },
    "securitySchemes": {
      "BearerAuth": {
        "bearerFormat": "JWT",
        "description": "JWT access token. Obtain via POST /api/v1/auth/login.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "title": "EMTITHAL API",
    "version": "0.3.1"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/admin/code-editions": {
      "post": {
        "description": "Create a new row in `compliance.code_editions` with `tenant_id = caller's tenant`. Returns the new edition id + presigned PUT URLs for the source PDFs the caller will upload.",
        "operationId": "create_code_edition_api_v1_admin_code_editions_post",
        "parameters": [
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCodeEditionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Edition created; upload URLs returned."
          },
          "401": {
            "description": "Not authenticated."
          },
          "403": {
            "description": "Requires licensed_engineer or admin role."
          },
          "422": {
            "description": "Validation error."
          }
        },
        "summary": "Register a new tenant-scoped code-edition",
        "tags": [
          "admin",
          "code-editions"
        ]
      }
    },
    "/api/v1/admin/code-editions/{edition_id}/ingest": {
      "post": {
        "description": "Starts `code_ingest.IngestCodeWorkflow` on Temporal with the tenant_id + code_edition_id threaded into the workflow input.",
        "operationId": "start_ingestion_api_v1_admin_code_editions__edition_id__ingest_post",
        "parameters": [
          {
            "in": "path",
            "name": "edition_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Edition Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Workflow submitted."
          },
          "401": {
            "description": "Not authenticated."
          },
          "403": {
            "description": "Requires licensed_engineer or admin role."
          },
          "404": {
            "description": "Code-edition not found (or RLS-hidden)."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "503": {
            "description": "Temporal unavailable."
          }
        },
        "summary": "Kick off the ingestion workflow for a code-edition",
        "tags": [
          "admin",
          "code-editions"
        ]
      }
    },
    "/api/v1/admin/code-editions/{edition_id}/review-queue": {
      "get": {
        "description": "Paginated list of rules awaiting engineer approval. RLS scopes the read to the caller's tenant; the handler only paginates.",
        "operationId": "list_review_queue_api_v1_admin_code_editions__edition_id__review_queue_get",
        "parameters": [
          {
            "in": "path",
            "name": "edition_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Edition Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "title": "Limit",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "minimum": 0,
              "title": "Offset",
              "type": "integer"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Review queue page returned."
          },
          "401": {
            "description": "Not authenticated."
          },
          "403": {
            "description": "Requires licensed_engineer or admin role."
          },
          "404": {
            "description": "Code-edition not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List rules pending engineer review for a code-edition",
        "tags": [
          "admin",
          "code-editions"
        ]
      }
    },
    "/api/v1/admin/code-editions/{edition_id}/status": {
      "get": {
        "description": "Return the ingestion lifecycle status + rule counts.",
        "operationId": "get_ingestion_status_api_v1_admin_code_editions__edition_id__status_get",
        "parameters": [
          {
            "in": "path",
            "name": "edition_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Edition Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Status returned."
          },
          "401": {
            "description": "Not authenticated."
          },
          "403": {
            "description": "Requires licensed_engineer or admin role."
          },
          "404": {
            "description": "Code-edition not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Return ingestion status + rule counts for a code-edition",
        "tags": [
          "admin",
          "code-editions"
        ]
      }
    },
    "/api/v1/auth/login": {
      "post": {
        "description": "Log in with email and password. On success, returns access token (JWT, typically 1h TTL) and sets httpOnly refresh cookie (14 days, SameSite=Lax). Use access token in Authorization: Bearer header for subsequent requests. Refresh token binds to the issued access token (H-24 binding prevents token mixups).",
        "operationId": "login_api_v1_auth_login_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Invalid credentials. Error code: INVALID_CREDENTIALS."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limited. Too many login attempts from this IP. Check retry_after_seconds."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limiter unavailable (Redis down). Error code: RATE_LIMITER_UNAVAILABLE."
          }
        },
        "summary": "Authenticate and obtain session tokens",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/auth/logout": {
      "post": {
        "description": "Invalidate the current session. The refresh token is revoked in Redis and the cookie is deleted. Subsequent API calls without a fresh access token will fail with 401. The access token itself remains valid until exp, but is typically discarded client-side.",
        "operationId": "logout_api_v1_auth_logout_post",
        "parameters": [
          {
            "in": "cookie",
            "name": "emtithal_refresh",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Emtithal Refresh"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Log out and invalidate session",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/auth/me": {
      "get": {
        "description": "Return the authenticated user's profile (email, tier, language preference, status). Requires valid Authorization: Bearer access_token header.",
        "operationId": "me_api_v1_auth_me_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserMeResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Missing or invalid access token"
          }
        },
        "summary": "Retrieve current user profile",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/auth/refresh": {
      "post": {
        "description": "Exchange the refresh cookie + current access token for a new access token pair. The refresh token is rotated on each call (H-24: binding to access_token.jti prevents reuse). Requires both the refresh cookie (httpOnly, sent automatically by the browser) and Authorization: Bearer header with the current access token (even if expired by exp claim). On refresh failure, the refresh cookie is cleared (user bounced to login).",
        "operationId": "refresh_api_v1_auth_refresh_post",
        "parameters": [
          {
            "in": "cookie",
            "name": "emtithal_refresh",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Emtithal Refresh"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Missing/invalid token or cookie"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Rotate access token using refresh cookie",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/auth/register": {
      "post": {
        "description": "Register a new user with email and password. A verification OTP is sent to the email; call POST /verify-email with the OTP to activate. Password must be 12+ characters, with complexity enforced (3 of 4 character classes or 16+ character passphrase).",
        "operationId": "register_api_v1_auth_register_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegisterResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Email already registered. Error code: EMAIL_ALREADY_REGISTERED."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limited. Too many registration attempts from this IP. Check retry_after_seconds."
          }
        },
        "summary": "Register a new user account",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/auth/request-password-reset": {
      "post": {
        "description": "Send a 6-digit password-reset code to the given email. The response is DELIBERATELY identical whether or not the email is registered — this endpoint cannot be used to discover which addresses have accounts. Call POST /reset-password with the code to complete.",
        "operationId": "request_password_reset_api_v1_auth_request_password_reset_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RequestPasswordResetRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestPasswordResetResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limited. Too many reset requests from this IP. Check retry_after_seconds."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limiter unavailable (Redis down). Retry after 5 seconds. Error code: RATE_LIMITER_UNAVAILABLE."
          }
        },
        "summary": "Request a password-reset code",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/auth/reset-password": {
      "post": {
        "description": "Set a new password using the 6-digit code from POST /request-password-reset. On success the password is changed and ALL existing sessions are revoked (the user must log in again). Errors are generic (no account-existence disclosure).",
        "operationId": "reset_password_api_v1_auth_reset_password_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResetPasswordRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResetPasswordResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Invalid or expired reset code. Error code: INVALID_OTP."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limited. Too many reset attempts for this email. Check retry_after_seconds."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limiter unavailable (Redis down). Error code: RATE_LIMITER_UNAVAILABLE."
          }
        },
        "summary": "Reset password using the emailed code",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/auth/verify-email": {
      "post": {
        "description": "Complete email verification by submitting the 6-digit OTP sent to the registered email. On success, returns access token and sets httpOnly refresh cookie. The refresh token is bound to the access token (H-24 binding).",
        "operationId": "verify_email_api_v1_auth_verify_email_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Invalid or expired OTP. Error code: INVALID_OTP."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limited. Too many verification attempts. Check retry_after_seconds."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthErrorResponse"
                }
              }
            },
            "description": "Rate limiter unavailable (Redis down). Retry after 5 seconds. Error code: RATE_LIMITER_UNAVAILABLE."
          }
        },
        "summary": "Verify email and activate account",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/v1/billing/me/subscription": {
      "delete": {
        "description": "Sets cancel_at_period_end=true.  The subscription remains 'active' until current_period_end, at which point a renewal worker transitions it to 'expired'.  Returns the updated subscription state.\n\nEnterprise commitments: cancellation BEFORE commitment_end_at is forbidden via this endpoint -- contact support for negotiated early termination.",
        "operationId": "cancel_my_subscription_api_v1_billing_me_subscription_delete",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "description": "No subscription found for caller."
          },
          "409": {
            "description": "Illegal state transition or commitment violation."
          }
        },
        "summary": "Soft-cancel the caller's subscription (effective at period end)",
        "tags": [
          "billing"
        ]
      },
      "get": {
        "description": "Returns the active subscription row for the authenticated user; scoped by RLS to the caller's tenant.  Returns 404 if the caller has no subscription yet (e.g. new sign-up before selecting a plan).",
        "operationId": "get_my_subscription_api_v1_billing_me_subscription_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Get the caller's subscription (RLS-scoped)",
        "tags": [
          "billing"
        ]
      },
      "post": {
        "description": "Creates a new billing subscription for the authenticated user.  Accepts plan_code, billing_mode, billing_cadence, and optional PAYG settings.  Returns 409 if a subscription already exists; call DELETE + reactivate to switch plans.  Returns 404 if the specified plan_code is not found in the catalog.",
        "operationId": "create_my_subscription_api_v1_billing_me_subscription_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSubscriptionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "description": "Plan not found."
          },
          "409": {
            "description": "Subscription already exists or plan incompatibility."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Create a subscription for the caller (sign-up)",
        "tags": [
          "billing"
        ]
      }
    },
    "/api/v1/billing/me/subscription/payg": {
      "patch": {
        "description": "Enables or disables pay-as-you-go metered billing for the caller's subscription and optionally sets a monthly SAR ceiling.  When opting out, the ceiling is cleared server-side.  Returns 422 if the plan does not support PAYG; returns 404 if no subscription exists.",
        "operationId": "update_my_payg_api_v1_billing_me_subscription_payg_patch",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePaygRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Toggle PAYG opt-in + monthly ceiling",
        "tags": [
          "billing"
        ]
      }
    },
    "/api/v1/billing/me/subscription/reactivate": {
      "post": {
        "description": "Reverses a prior soft-cancel by clearing cancel_at_period_end.  Only valid while the subscription is still 'active' and before current_period_end.  Returns 404 if no subscription exists, 409 if the subscription is already expired or the state transition is illegal.",
        "operationId": "reactivate_my_subscription_api_v1_billing_me_subscription_reactivate_post",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionResponse"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Undo a soft-cancel before the period ends",
        "tags": [
          "billing"
        ]
      }
    },
    "/api/v1/billing/me/usage": {
      "get": {
        "description": "Returns a snapshot of the caller's billing.usage_meters row covering the present moment.  Useful for in-app dashboards showing 'X reviews remaining', 'Y GB used', running PAYG charges, etc.  Returns 404 if the caller has no active subscription (and therefore no current meter).",
        "operationId": "get_my_usage_api_v1_billing_me_usage_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageMeterResponse"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Get the caller's current-period usage meter",
        "tags": [
          "billing"
        ]
      }
    },
    "/api/v1/billing/plans": {
      "get": {
        "description": "Returns the public plan catalog ordered Free -> Pro -> Enterprise.  All authenticated users see the same list (catalog is not RLS-scoped).  Useful for billing UI: render the pricing page from this response.",
        "operationId": "list_plans_api_v1_billing_plans_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/PlanResponse"
                  },
                  "title": "Response List Plans Api V1 Billing Plans Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "List published plan catalog",
        "tags": [
          "billing"
        ]
      }
    },
    "/api/v1/billing/plans/{plan_code}": {
      "get": {
        "description": "Returns the full billing.plans row for a single plan identified by its code (e.g. 'free', 'pro', 'enterprise').  Useful for building plan-detail or upgrade-confirmation screens.  Returns 404 if the plan code is unknown.",
        "operationId": "get_plan_api_v1_billing_plans__plan_code__get",
        "parameters": [
          {
            "in": "path",
            "name": "plan_code",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/PlanCode"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Get a single plan by code",
        "tags": [
          "billing"
        ]
      }
    },
    "/api/v1/billing/webhooks/moyasar": {
      "post": {
        "description": "Public endpoint receiving Moyasar payment.* and subscription.* webhook deliveries.  Authentication via merchant-assigned secret_token field in the JSON body (AUD-120).  Returns 202 on success or unknown event; 401 on token mismatch; 400 on malformed payload.",
        "operationId": "moyasar_payment_webhook_api_v1_billing_webhooks_moyasar_post",
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "title": "Response Moyasar Payment Webhook Api V1 Billing Webhooks Moyasar Post",
                  "type": "object"
                }
              }
            },
            "description": "Webhook accepted (processed or no-op)"
          },
          "400": {
            "description": "Malformed JSON or missing required field"
          },
          "401": {
            "description": "Body-token verification failed"
          },
          "422": {
            "description": "Payload schema validation error"
          },
          "503": {
            "description": "Subscription not found (payment.succeeded) — retryable"
          }
        },
        "security": [],
        "summary": "Moyasar payment + subscription webhook intake (body-token verified)",
        "tags": [
          "webhooks-moyasar"
        ]
      }
    },
    "/api/v1/codes": {
      "get": {
        "description": "Returns the multi-code platform inventory. Platform codes (tenant_id IS NULL) are visible to every caller; tenant BYO codes (Phase 4) are visible only to the owning tenant via RLS.",
        "operationId": "list_codes_api_v1_codes_get",
        "parameters": [
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "codes": [
                    {
                      "code_id": "801",
                      "code_long_name": "Saudi Building Code 801 - Fire Code",
                      "editions": [
                        {
                          "id": "00000000-0000-0000-0000-000000000001",
                          "jurisdiction": "SA",
                          "language": "ar+en",
                          "year": 2023
                        }
                      ]
                    }
                  ]
                },
                "schema": {
                  "additionalProperties": true,
                  "title": "Response List Codes Api V1 Codes Get",
                  "type": "object"
                }
              }
            },
            "description": "Code-editions grouped by code_id."
          },
          "401": {
            "description": "Not authenticated."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List code-editions visible to the caller",
        "tags": [
          "codes"
        ]
      }
    },
    "/api/v1/codes/{code_id}/qa": {
      "post": {
        "description": "Returns the top-K most-similar rules + an LLM-synthesized answer. Tenant-scoped via RLS on `compliance.code_editions` + `compliance.rules`. Every successful interaction emits a `CODE_QA_INTERACTION` audit event.",
        "operationId": "code_qa_api_v1_codes__code_id__qa_post",
        "parameters": [
          {
            "in": "path",
            "name": "code_id",
            "required": true,
            "schema": {
              "maxLength": 50,
              "minLength": 1,
              "title": "Code Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QARequestBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "answer_text": "Minimum exit width per SBC 801 §7.4.2 is...",
                  "citations": [
                    {
                      "code_edition_id": "00000000-0000-0000-0000-000000000001",
                      "relevance_score": 0.83,
                      "rule_id": "801-7.4.2",
                      "rule_text_ar": "...",
                      "rule_text_en": "...",
                      "section_number": "7.4.2"
                    }
                  ],
                  "lang": "en",
                  "model_version": "stub-llm-v0",
                  "prompt_hash": "sha256:abc...",
                  "prompt_version": "system_qa_en.v1"
                },
                "schema": {
                  "$ref": "#/components/schemas/QAResponse"
                }
              }
            },
            "description": "Answer + citations."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Code not visible to the caller."
          },
          "422": {
            "description": "Validation error."
          }
        },
        "summary": "Ask a question about a code's rules (bilingual, with citations)",
        "tags": [
          "codes",
          "qa"
        ]
      }
    },
    "/api/v1/jobs": {
      "post": {
        "description": "Creates a new compliance job for the given project + document set, starts the ComplianceJobWorkflow on Temporal, and returns the job_id immediately. The job runs asynchronously; poll GET /jobs/{job_id} or stream GET /jobs/{job_id}/events for progress.",
        "operationId": "create_job_api_v1_jobs_post",
        "parameters": [
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobCreateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            },
            "description": "Job created."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Not authenticated."
          },
          "403": {
            "description": "Forbidden."
          },
          "404": {
            "description": "Project or one of the documents not found."
          },
          "422": {
            "description": "Validation error."
          },
          "503": {
            "description": "Temporal unavailable; job rows exist but workflow not started."
          }
        },
        "summary": "Create a compliance check job",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}": {
      "get": {
        "description": "Returns the current state of a compliance job. When status='completed', the response includes all verdicts and drawing elements.",
        "operationId": "get_job_api_v1_jobs__job_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            },
            "description": "Job found."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Job not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Get compliance job state",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/cancel": {
      "post": {
        "description": "Sends a cancellation signal to the Temporal ComplianceJobWorkflow. Licensed_engineer-only.",
        "operationId": "cancel_job_api_v1_jobs__job_id__cancel_post",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Cancellation signal sent."
          },
          "401": {
            "description": "Not authenticated."
          },
          "403": {
            "description": "Not a licensed engineer."
          },
          "404": {
            "description": "Job not found."
          },
          "409": {
            "description": "Job is not in a cancellable state."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "503": {
            "description": "Temporal unavailable."
          }
        },
        "summary": "Cancel a running compliance job",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/events": {
      "get": {
        "description": "Server-Sent Events stream of compliance job progress. Reconnect with Last-Event-ID to resume from the last received sequence.",
        "operationId": "stream_job_events_api_v1_jobs__job_id__events_get",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "last-event-id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Last-Event-Id"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              },
              "text/event-stream": {
                "example": "event: rule_evaluated\nid: 1\ndata: {...}\n\n",
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "SSE stream of job progress events."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Job not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Stream job progress events (SSE)",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/override": {
      "post": {
        "description": "Licensed engineer overrides a compliance verdict. Persisted to projects.job_overrides AND signals the Temporal workflow.",
        "operationId": "override_verdict_api_v1_jobs__job_id__override_post",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OverrideRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Override recorded and workflow signalled."
          },
          "401": {
            "description": "Not authenticated."
          },
          "403": {
            "description": "Not a licensed engineer."
          },
          "404": {
            "description": "Job or rule_id not found."
          },
          "409": {
            "description": "original_verdict mismatch — the current effective verdict differs from the one the override was issued against."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Override a compliance verdict",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/result.pdf": {
      "get": {
        "description": "Returns an S3 presigned URL for the compliance verdict PDF. Valid for 15 minutes.",
        "operationId": "get_result_pdf_api_v1_jobs__job_id__result_pdf_get",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Presigned URL + expiry."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Job not found or PDF not yet generated."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Get verdict PDF download URL",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/rules/{rule_id}/verdicts": {
      "get": {
        "description": "AUD-048: returns ALL element-grained verdict rows for a single rule_id at the current (MAX) version — one row per cited_drawing_element_id. No rollup is applied here; both the FAIL row for door-A and the PASS row for door-B are returned so the workbench can deep-link each element to its position on the canvas. Paginated.",
        "operationId": "get_rule_element_verdicts_api_v1_jobs__job_id__rules__rule_id__verdicts_get",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "rule_id",
            "required": true,
            "schema": {
              "title": "Rule Id",
              "type": "string"
            }
          },
          {
            "description": "Filter by verdict value (PASS / FAIL / NEEDS_REVIEW / OVERRIDDEN / NOT_APPLICABLE).",
            "in": "query",
            "name": "verdict",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by verdict value (PASS / FAIL / NEEDS_REVIEW / OVERRIDDEN / NOT_APPLICABLE).",
              "examples": [
                "FAIL"
              ],
              "title": "Verdict"
            }
          },
          {
            "description": "Page size (1-500).",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 200,
              "description": "Page size (1-500).",
              "maximum": 500,
              "minimum": 1,
              "title": "Limit",
              "type": "integer"
            }
          },
          {
            "description": "Zero-based offset.",
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "description": "Zero-based offset.",
              "minimum": 0,
              "title": "Offset",
              "type": "integer"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Per-element verdicts for the rule."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Job not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Per-element verdict drill-down for a single rule (AUD-048)",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/verdicts": {
      "get": {
        "description": "Returns the compliance verdicts for a job as a paginated flat list. AUD-048: verdicts are worst-of-rolled-up per rule_id — a FAIL on any element surfaces as FAIL at rule grain, never masked by a PASS row. AUD-053: results are paginated (limit/offset); total_count is returned so the frontend can render a page indicator. Each verdict carries the multi-code platform fields (code_id, code_edition_id, element_id, citations) per docs/api/verdict-shape.md.",
        "operationId": "list_job_verdicts_api_v1_jobs__job_id__verdicts_get",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "description": "Filter by design-code identifier (e.g. '801', '201').",
            "in": "query",
            "name": "code_id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by design-code identifier (e.g. '801', '201').",
              "examples": [
                "801"
              ],
              "title": "Code Id"
            }
          },
          {
            "description": "Filter by drawing element type (e.g. 'door', 'corridor').",
            "in": "query",
            "name": "element_type",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by drawing element type (e.g. 'door', 'corridor').",
              "examples": [
                "door"
              ],
              "title": "Element Type"
            }
          },
          {
            "description": "Filter by verdict state. Accepts the same values as VerdictValue (PASS / FAIL / NEEDS_REVIEW / OVERRIDDEN / NOT_APPLICABLE).",
            "in": "query",
            "name": "verdict_state",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by verdict state. Accepts the same values as VerdictValue (PASS / FAIL / NEEDS_REVIEW / OVERRIDDEN / NOT_APPLICABLE).",
              "examples": [
                "NEEDS_REVIEW"
              ],
              "title": "Verdict State"
            }
          },
          {
            "description": "Maximum number of verdicts to return per page (1-500). Default: 200.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 200,
              "description": "Maximum number of verdicts to return per page (1-500). Default: 200.",
              "maximum": 500,
              "minimum": 1,
              "title": "Limit",
              "type": "integer"
            }
          },
          {
            "description": "Zero-based offset for pagination.",
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "description": "Zero-based offset for pagination.",
              "minimum": 0,
              "title": "Offset",
              "type": "integer"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerdictsListResponse"
                }
              }
            },
            "description": "List of verdicts."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Job not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List job verdicts in Phase-7 workbench shape (paginated)",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/versions": {
      "post": {
        "description": "Creates a new immutable version row in `projects.job_versions`. Multi-version-per-job is a Phase 5 deliverable — the table is currently 1:1 with `projects.jobs` so this endpoint is wired as a 501 scaffold. The contract shape (returns `version_id`) is stable so the CTO frontend can be implemented in parallel.",
        "operationId": "create_version_api_v1_jobs__job_id__versions_post",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Version created. Returns `version_id`."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Job not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "501": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Phase 5 dependency: multi-version schema not yet shipped."
          }
        },
        "summary": "Create a new working version of a job (Phase 5 scaffold)",
        "tags": [
          "versions"
        ]
      }
    },
    "/api/v1/jobs/{job_id}/versions/{version_a}/diff/{version_b}": {
      "get": {
        "description": "Returns `{resolved, new_violations, unchanged}` — the set diff of (element_id, code_id, rule_id, verdict_state) tuples between two versions' verdicts. Used by the workbench to render red/green diff highlights after an engineer's edit triggers re-evaluation. \n\nPhase-5 dependency: multi-version-per-job schema not yet shipped. Until that lands, the only valid request is the self-diff (version_a == version_b == job_id), which returns {resolved: [], new_violations: [], unchanged: <all current verdicts>}. Cross-version requests get a 501.",
        "operationId": "diff_versions_api_v1_jobs__job_id__versions__version_a__diff__version_b__get",
        "parameters": [
          {
            "in": "path",
            "name": "job_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Job Id",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "version_a",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Version A",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "version_b",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Version B",
              "type": "string"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "job_id": "00000000-0000-0000-0000-000000000001",
                  "new_violations": [],
                  "resolved": [],
                  "unchanged": [
                    {
                      "code_id": "801",
                      "element_id": "...",
                      "rule_id": "SBC801-1005.1",
                      "verdict_state": "PASS"
                    }
                  ],
                  "version_a": "00000000-0000-0000-0000-000000000001",
                  "version_b": "00000000-0000-0000-0000-000000000001"
                },
                "schema": {}
              }
            },
            "description": "Diff result."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Job or version not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          },
          "501": {
            "description": "Cross-version diff blocked on Phase 5 schema work."
          }
        },
        "summary": "Diff verdicts between two versions of a job (Phase 9)",
        "tags": [
          "versions"
        ]
      }
    },
    "/api/v1/projects": {
      "get": {
        "description": "Returns all projects owned by the authenticated user, scoped by RLS to the caller's tenant.  The list is unordered; clients should sort by created_at if display order matters.",
        "operationId": "list_projects_api_v1_projects_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/ProjectResponse"
                  },
                  "title": "Response List Projects Api V1 Projects Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "List your projects",
        "tags": [
          "projects"
        ]
      },
      "post": {
        "description": "Creates a new project (container for uploaded drawings) owned by the authenticated user.  The project name must be 1-200 characters.  Returns the new project row with its UUID; pass this id to POST /api/v1/projects/{project_id}/documents to begin uploading drawings.",
        "operationId": "create_project_api_v1_projects_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProjectCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Create a project",
        "tags": [
          "projects"
        ]
      }
    },
    "/api/v1/projects/{project_id}/documents": {
      "post": {
        "description": "Mints a presigned S3 PUT URL so the client can upload a drawing file directly to S3 without transiting the API.  The returned document_id and upload_url are valid for 15 minutes.  After the PUT completes, call POST .../documents/{document_id}/complete to register the document row.  Returns 404 if the project is not found or the caller does not own it.",
        "operationId": "register_document_api_v1_projects__project_id__documents_post",
        "parameters": [
          {
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Project Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentRegister"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentUploadResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Register a document + get a presigned upload URL",
        "tags": [
          "projects"
        ]
      }
    },
    "/api/v1/projects/{project_id}/documents/{document_id}/complete": {
      "post": {
        "description": "Verifies that the client's PUT to upload_url succeeded, computes the SHA-256 hash of the uploaded object, enforces the per-user storage quota, and inserts the project_documents row.  Idempotent: retrying an already-completed document_id returns the existing row.  Returns 409 if the object has not been uploaded yet; 413 if it exceeds the 200 MB limit; 402 if the storage quota or PAYG ceiling is exhausted.",
        "operationId": "complete_document_api_v1_projects__project_id__documents__document_id__complete_post",
        "parameters": [
          {
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Project Id",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "document_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Document Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DocumentComplete"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Finalize an upload (head + hash the object, register the document)",
        "tags": [
          "projects"
        ]
      }
    },
    "/api/v1/projects/{project_id}/jobs": {
      "get": {
        "description": "Returns recent compliance jobs for the given project, ordered by created_at descending. Paginated via `limit` and `offset`.",
        "operationId": "list_project_jobs_api_v1_projects__project_id__jobs_get",
        "parameters": [
          {
            "in": "path",
            "name": "project_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Project Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "title": "Limit",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "minimum": 0,
              "title": "Offset",
              "type": "integer"
            }
          },
          {
            "in": "header",
            "name": "accept-language",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Accept-Language"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/JobResponse"
                  },
                  "title": "Response 200 List Project Jobs Api V1 Projects  Project Id  Jobs Get",
                  "type": "array"
                }
              }
            },
            "description": "List of jobs."
          },
          "401": {
            "description": "Not authenticated."
          },
          "404": {
            "description": "Project not found."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List compliance jobs for a project",
        "tags": [
          "jobs"
        ]
      }
    },
    "/api/v1/rule-review/pdf/{filename}": {
      "get": {
        "description": "Retrieve an SBC rule PDF in either Arabic or English. Only files in the approved manifest set are served (SBC-301-AR.pdf, SBC-301-EN.pdf, SBC-201-AR.pdf, SBC-201-EN.pdf). Path traversal defenses are in place (F-18: canonical path check). Requires authentication.",
        "operationId": "serve_pdf_api_v1_rule_review_pdf__filename__get",
        "parameters": [
          {
            "in": "path",
            "name": "filename",
            "required": true,
            "schema": {
              "title": "Filename",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "description": "PDF not in manifest or file missing on disk"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Download SBC rule PDF",
        "tags": [
          "rule-review"
        ]
      }
    },
    "/api/v1/rule-review/queue": {
      "get": {
        "description": "Retrieve paginated review queue items visible to the authenticated user. Filters: status (pending, approved, rejected), rule_type. Results use row-level security (RLS) so users only see items in their review groups.",
        "operationId": "list_queue_api_v1_rule_review_queue_get",
        "parameters": [
          {
            "description": "Filter by queue item status",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by queue item status",
              "title": "Status"
            }
          },
          {
            "description": "Filter by SBC rule type (e.g., SBC-301, SBC-201)",
            "in": "query",
            "name": "rule_type",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Filter by SBC rule type (e.g., SBC-301, SBC-201)",
              "title": "Rule Type"
            }
          },
          {
            "description": "Page size (1-500)",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "description": "Page size (1-500)",
              "maximum": 500,
              "minimum": 1,
              "title": "Limit",
              "type": "integer"
            }
          },
          {
            "description": "Pagination offset",
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "description": "Pagination offset",
              "minimum": 0,
              "title": "Offset",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "title": "Response List Queue Api V1 Rule Review Queue Get",
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List rule review queue items",
        "tags": [
          "rule-review"
        ]
      }
    },
    "/api/v1/rule-review/queue/{queue_id}": {
      "get": {
        "description": "Retrieve full details of a queue item including rule data, extracted values, and pending edits. Subject to RLS (user must be in the queue item's review group). Returns 404 if item not found or user lacks access.",
        "operationId": "get_queue_item_api_v1_rule_review_queue__queue_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "queue_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Queue Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "title": "Response Get Queue Item Api V1 Rule Review Queue  Queue Id  Get",
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "description": "Queue item not found. Error code: QUEUE_ITEM_NOT_FOUND"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Get detailed review queue item",
        "tags": [
          "rule-review"
        ]
      },
      "patch": {
        "description": "Update specific fields of a pending review item. Each edited field must have a correction_reason explaining the change. The item remains in 'pending_review' status. Requires licensed_engineer role. Subject to RLS.",
        "operationId": "patch_queue_item_api_v1_rule_review_queue__queue_id__patch",
        "parameters": [
          {
            "in": "path",
            "name": "queue_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Queue Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "title": "Response Patch Queue Item Api V1 Rule Review Queue  Queue Id  Patch",
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "description": "Queue item not found. Error code: QUEUE_ITEM_NOT_FOUND"
          },
          "409": {
            "description": "Item not in pending_review status. Error code: QUEUE_ITEM_NOT_PENDING_REVIEW"
          },
          "422": {
            "description": "Field validation failed. Error code: INVALID_FIELD_EDIT or PLAUSIBILITY_VIOLATION"
          }
        },
        "summary": "Apply incremental corrections to pending review item",
        "tags": [
          "rule-review"
        ]
      }
    },
    "/api/v1/rule-review/queue/{queue_id}/approve": {
      "post": {
        "description": "Finalize review by approving the rule. Any pending field_edits are applied as final. A RULE_REVIEW_APPROVED audit event is emitted with the approval timestamp and reviewer ID. Item transitions to 'approved' status. The rule is now live for compliance checks. Requires licensed_engineer role. Subject to RLS.",
        "operationId": "approve_queue_item_api_v1_rule_review_queue__queue_id__approve_post",
        "parameters": [
          {
            "in": "path",
            "name": "queue_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Queue Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ApproveBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "title": "Response Approve Queue Item Api V1 Rule Review Queue  Queue Id  Approve Post",
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "description": "Queue item not found. Error code: QUEUE_ITEM_NOT_FOUND"
          },
          "409": {
            "description": "Item not in pending_review status. Error code: QUEUE_ITEM_NOT_PENDING_REVIEW"
          },
          "422": {
            "description": "Final field validation failed. Error code: INVALID_FIELD_EDIT or PLAUSIBILITY_VIOLATION"
          }
        },
        "summary": "Approve rule and finalize review",
        "tags": [
          "rule-review"
        ]
      }
    },
    "/api/v1/rule-review/queue/{queue_id}/reject": {
      "post": {
        "description": "Reject a pending review item with a reason. A RULE_REVIEW_REJECTED audit event is emitted. The rule is returned to the submission queue for reprocessing (re-extraction or re-entry). The reason is stored for the compliance team. Requires licensed_engineer role. Subject to RLS.",
        "operationId": "reject_queue_item_api_v1_rule_review_queue__queue_id__reject_post",
        "parameters": [
          {
            "in": "path",
            "name": "queue_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Queue Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RejectBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "title": "Response Reject Queue Item Api V1 Rule Review Queue  Queue Id  Reject Post",
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "description": "Queue item not found. Error code: QUEUE_ITEM_NOT_FOUND"
          },
          "409": {
            "description": "Item not in pending_review status. Error code: QUEUE_ITEM_NOT_PENDING_REVIEW"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Reject rule and return to submission queue",
        "tags": [
          "rule-review"
        ]
      }
    },
    "/api/v1/users/me/dsar": {
      "post": {
        "description": "Authenticated data subject (user) submits a DSAR.  Request lands in the manual review queue and is resolved within 30 days per PDPL Article 22.  Returns 202 with the request_id and the resolution deadline timestamp.",
        "operationId": "submit_dsar_api_v1_users_me_dsar_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DsarRequestBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DsarAck"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Submit a PDPL data-subject access/erasure request",
        "tags": [
          "users",
          "pdpl"
        ]
      }
    },
    "/api/v1/webhooks/subscriptions": {
      "get": {
        "description": "Returns all webhook subscriptions belonging to organisations the caller is a member of, ordered by created_at descending.  RLS at the DB layer enforces organisation-scoped visibility so cross-tenant leakage is impossible.",
        "operationId": "list_subscriptions_api_v1_webhooks_subscriptions_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/WebhookSubscriptionResponse"
                  },
                  "title": "Response List Subscriptions Api V1 Webhooks Subscriptions Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "List webhook subscriptions visible to caller (RLS-scoped)",
        "tags": [
          "webhooks"
        ]
      },
      "post": {
        "description": "Creates a new webhook subscription that delivers HMAC-signed event payloads to the specified HTTPS endpoint_url.  The signing_secret is stored hashed and never returned; keep the plaintext to verify delivery signatures.  Returns 402 if no active subscription exists or the plan limit is reached; 422 if the endpoint_url is not a publicly-routable HTTPS address; 403 if the caller is not a member of the specified organisation.",
        "operationId": "create_subscription_api_v1_webhooks_subscriptions_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookSubscriptionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Register a webhook subscription",
        "tags": [
          "webhooks"
        ]
      }
    },
    "/api/v1/webhooks/subscriptions/{subscription_id}": {
      "delete": {
        "description": "Sets is_active=false.  Hard-deletion is admin-only via direct SQL\n(preserves audit history of past subscriptions).  Returns 204 even if\nthe row is already inactive (idempotent).",
        "operationId": "delete_subscription_api_v1_webhooks_subscriptions__subscription_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "subscription_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Subscription Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "404": {
            "description": "Webhook subscription not found or RLS-hidden."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Soft-delete (deactivate) a webhook subscription",
        "tags": [
          "webhooks"
        ]
      }
    },
    "/api/v1/webhooks/subscriptions/{subscription_id}/test": {
      "post": {
        "description": "Send a `webhook.test` event payload to the customer's endpoint with\nthe HMAC signature.  Returns the customer's HTTP status + latency + any\ntransport error.  Always returns HTTP 200 with the result in the body\n(the customer's failure shouldn't surface as our HTTP failure).",
        "operationId": "test_ping_api_v1_webhooks_subscriptions__subscription_id__test_post",
        "parameters": [
          {
            "in": "path",
            "name": "subscription_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Subscription Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestPingResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Send a synchronous test ping to verify endpoint setup",
        "tags": [
          "webhooks"
        ]
      }
    },
    "/healthz": {
      "get": {
        "description": "Return 200 if the process event loop is responsive. This probe does not check external dependencies; it always succeeds unless the process is frozen or crashing.",
        "operationId": "liveness_healthz_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "security": [],
        "summary": "Liveness probe",
        "tags": [
          "health",
          "health"
        ]
      }
    },
    "/readyz": {
      "get": {
        "description": "Return 200 iff both Aurora PostgreSQL and Redis are reachable. Kubernetes removes the pod from the service load balancer when this returns non-200. Checks are optimized for speed (<2s) using connection-pool pings, not real queries.",
        "operationId": "readiness_readyz_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceUnavailableError"
                }
              }
            },
            "description": "Database or Redis unavailable"
          }
        },
        "security": [],
        "summary": "Readiness probe",
        "tags": [
          "health",
          "health"
        ]
      }
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ]
}
