{"openapi":"3.1.0","info":{"title":"Maditon API","description":"AI-powered EU regulatory compliance intelligence for SMEs. Maditon helps organisations register their AI systems, classify risk under the EU AI Act, and track compliance obligations.","version":"0.2.0"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Healthcheck","description":"Return service health status.","operationId":"healthcheck_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Healthcheck Health Get"}}}}}}},"/api/auth/bootstrap":{"post":{"tags":["Auth"],"summary":"First-login bootstrap","description":"Idempotently creates the User and Organisation rows for the authenticated Zitadel subject and returns the current auth state. Safe to call on every login — existing users are returned unchanged (with email/name kept in sync with Zitadel).","operationId":"bootstrap_api_auth_bootstrap_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStateResponse"}}}}}}},"/api/auth/state":{"get":{"tags":["Auth"],"summary":"Get current auth state","description":"Returns the minimal auth state consumed by the Next.js middleware to decide whether the user should land on /pricing, /onboarding or the app. Lazily bootstraps the User + Organisation rows when they are missing so older test users pick up the new flow.","operationId":"state_api_auth_state_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthStateResponse"}}}}}}},"/api/users/me":{"get":{"tags":["users"],"summary":"Get Current User Profile","description":"Return the authenticated user's profile.\n\nReads the email straight from Zitadel rather than the JWT so that\na confirmed email change is visible to the settings page before\nthe user's next token refresh. Falls back to the JWT claim when\nthe IdP call fails — settings must keep rendering through\ntransient Zitadel flakiness. Persists any drift into ``users.email``\nso downstream readers (billing receipts, audit) stay aligned without\nwaiting for the next login bootstrap.","operationId":"get_current_user_profile_api_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}}},"patch":{"tags":["users"],"summary":"Update Current User Profile","description":"Update the authenticated user's profile.\n\nSupports ``language`` and the three notification toggles — kept\ndeliberately narrow so new fields land as explicit additions to\n``UpdateMeRequest`` rather than an accidental broad patch surface.","operationId":"update_current_user_profile_api_users_me_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMeRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/me/profile":{"patch":{"tags":["users"],"summary":"Update Profile","description":"Change the authenticated user's given and family name in Zitadel.\n\nOn a successful Zitadel update we mirror the joined name into\n``users.name`` so the rest of the product (audit log labels,\nlistings) reflects the change immediately rather than waiting for\nthe next bootstrap sync.","operationId":"update_profile_api_users_me_profile_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProfileRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/me/email-change":{"post":{"tags":["users"],"summary":"Initiate Email Change","description":"Begin an email-change flow.\n\nCalls Zitadel's ``set_email`` with ``sendCode``: Zitadel updates\nthe address to the new value but leaves it *unverified* and emails\na verification link to it. The user clicks that link to complete\nthe change in Zitadel's hosted UI — Maditon's settings page does\nnot collect the code itself.","operationId":"initiate_email_change_api_users_me_email_change_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChangeInitiateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailChangeInitiateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/users/me/password":{"post":{"tags":["users"],"summary":"Change Password","description":"Change the authenticated user's password.\n\nZitadel validates the current password and enforces the org\npassword policy (length, breach check, history). Policy violations\nsurface as ``zitadel:<code>`` HTTP errors and the frontend either\nmaps known codes to localised copy or shows Zitadel's ``message``\nas a fallback.","operationId":"change_password_api_users_me_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordChangeRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-systems":{"post":{"tags":["AI Systems"],"summary":"Register a new AI system","description":"Register an AI system for compliance tracking. The system will be created with risk_level='unclassified' until the risk classification endpoint is called.","operationId":"create_ai_system_api_ai_systems_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["AI Systems"],"summary":"List AI systems for the organisation","description":"Returns a paginated list of AI systems registered by the authenticated user's organisation. Supports filtering by risk level, domain, role, and text search.","operationId":"list_ai_systems_api_ai_systems_get","parameters":[{"name":"risk_level","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by risk level.","title":"Risk Level"},"description":"Filter by risk level."},{"name":"domain","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by business domain.","title":"Domain"},"description":"Filter by business domain."},{"name":"role","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by org role.","title":"Role"},"description":"Filter by org role."},{"name":"search","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Search name/description.","title":"Search"},"description":"Search name/description."},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pagination cursor.","title":"Cursor"},"description":"Pagination cursor."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Page size.","default":50,"title":"Limit"},"description":"Page size."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-systems/inactive":{"get":{"tags":["AI Systems"],"summary":"List inactive (soft-deleted) AI systems","description":"Returns all soft-deleted AI systems for the organisation, ordered by deletion date descending.","operationId":"list_inactive_ai_systems_api_ai_systems_inactive_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AISystemResponse"},"type":"array","title":"Response List Inactive Ai Systems Api Ai Systems Inactive Get"}}}}}}},"/api/ai-systems/{system_id}":{"get":{"tags":["AI Systems"],"summary":"Get an AI system by ID","operationId":"get_ai_system_api_ai_systems__system_id__get","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["AI Systems"],"summary":"Update an AI system","description":"Partially update an AI system. Only provided fields are changed.","operationId":"update_ai_system_api_ai_systems__system_id__patch","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["AI Systems"],"summary":"Delete an AI system","description":"Soft-deletes an AI system. Data is retained for GDPR compliance.","operationId":"delete_ai_system_api_ai_systems__system_id__delete","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-systems/{system_id}/transparency":{"put":{"tags":["AI Systems"],"summary":"Publish or unpublish a system on the public transparency page","description":"Sets a system's public transparency state (audit F-08). Publishing requires org admin + recent authentication, the system must already be human-accepted, and a sanitized public_name must be supplied — the public page never shows the internal name/purpose verbatim. Unpublishing clears the publication record.","operationId":"set_system_transparency_api_ai_systems__system_id__transparency_put","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemTransparencyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemResponse"}}}},"409":{"description":"The system's risk classification has not been human-accepted."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ai-systems/{system_id}/reactivate":{"post":{"tags":["AI Systems"],"summary":"Reactivate a soft-deleted AI system","description":"Restores a soft-deleted AI system, making it active again.","operationId":"reactivate_ai_system_api_ai_systems__system_id__reactivate_post","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/risk-classification/classify/{system_id}":{"post":{"tags":["Risk Classification"],"summary":"Start an async risk classification","description":"Kicks off a background task that runs AI-powered risk classification against the EU AI Act (Regulation 2024/1689). Returns 202 Accepted with a job id and the initial ``status=pending`` / ``stage=queued`` snapshot. Poll ``GET /jobs/{job_id}`` to track progress and pick up the final ``RiskClassificationResult`` once ``status=completed``.","operationId":"classify_risk_api_risk_classification_classify__system_id__post","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}},{"name":"language","in":"query","required":false,"schema":{"type":"string","description":"Language for the classification reasoning (ISO 639-1).","default":"en","title":"Language"},"description":"Language for the classification reasoning (ISO 639-1)."}],"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassificationJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/risk-classification/jobs/{job_id}":{"get":{"tags":["Risk Classification"],"summary":"Poll an async classification job","description":"Returns the current snapshot of a classification job. While the background task runs, ``stage`` advances through ``retrieving`` → ``classifying`` → ``verifying`` → ``validating`` → ``persisting``. When ``status=completed`` the ``result`` field carries the full ``RiskClassificationResult``; when ``status=failed`` the ``error_message`` field carries the human-readable cause.","operationId":"get_classification_job_api_risk_classification_jobs__job_id__get","parameters":[{"name":"job_id","in":"path","required":true,"schema":{"type":"string","title":"Job Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassificationJobResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/risk-classification/{system_id}/accept":{"post":{"tags":["Risk Classification"],"summary":"Accept an AI-generated risk classification","description":"Explicitly accepts the AI-generated risk classification for an AI system. Requires admin or owner role (F-31). Records the accepting user and timestamp as an on-record act of accountability. Generates the compliance checklist after acceptance. No system may reach 'compliant' status without a prior acceptance. Re-classification resets the acceptance, requiring a new review.","operationId":"accept_risk_classification_api_risk_classification__system_id__accept_post","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiskClassificationAcceptRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AISystemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/risk-classification/{system_id}/latest":{"get":{"tags":["Risk Classification"],"summary":"Get the latest risk classification for a system","description":"Returns the most recent classification run for this system, or null if none exists.","operationId":"get_latest_classification_api_risk_classification__system_id__latest_get","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/StoredRiskClassification"},{"type":"null"}],"title":"Response Get Latest Classification Api Risk Classification  System Id  Latest Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/risk-classification/override/{system_id}":{"post":{"tags":["Risk Classification"],"summary":"Override risk classification","description":"Human override of an AI-generated risk classification. Requires admin or owner role. The override is recorded in the audit trail with the original classification preserved.","operationId":"override_risk_api_risk_classification_override__system_id__post","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiskOverrideRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RiskOverrideResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/risk-classification/{system_id}/reviewer-notes":{"patch":{"tags":["Risk Classification"],"summary":"Update reviewer notes on the accepted classification","description":"Updates the reviewer notes on the most recent accepted classification without re-running the AI classification. Pass null to clear the notes.","operationId":"update_reviewer_notes_api_risk_classification__system_id__reviewer_notes_patch","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewerNotesUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/StoredRiskClassification"},{"type":"null"}],"title":"Response Update Reviewer Notes Api Risk Classification  System Id  Reviewer Notes Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/regulation-chunks/by-article":{"get":{"tags":["Regulation Chunks"],"summary":"Get the full content of a regulation chunk by canonical article label.","description":"Looks up a chunk by ``(source, article_number, language)`` rather than chunk id. Used by the in-prose article-reference linkifier: when reasoning or unsupported-claim text says ``Artikel 50(1)``, the UI extracts the canonical ``Article 50`` label and calls this endpoint to render the dialog. Returns 404 when no chunk matches in the requested language; the caller can fall back to English or hide the link.","operationId":"get_regulation_chunk_by_article_api_regulation_chunks_by_article_get","parameters":[{"name":"article","in":"query","required":true,"schema":{"type":"string","title":"Article"}},{"name":"source","in":"query","required":false,"schema":{"type":"string","default":"eu_ai_act","title":"Source"}},{"name":"language","in":"query","required":false,"schema":{"type":"string","default":"en","title":"Language"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegulationChunkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/regulation-chunks/{chunk_id}":{"get":{"tags":["Regulation Chunks"],"summary":"Get the full content of one regulation chunk by id.","description":"Returns the complete article / annex text for a citation. The client typically reaches this endpoint by clicking 'Show full article' on a citation card rendered with an 800-char excerpt; the response carries the full text plus an EUR-Lex permalink.","operationId":"get_regulation_chunk_api_regulation_chunks__chunk_id__get","parameters":[{"name":"chunk_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Chunk Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegulationChunkResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/citation-feedback":{"post":{"tags":["Citation feedback"],"summary":"Flag a citation as incorrect.","description":"Records a reviewer's feedback that an AI-generated citation is wrong. Upserts on (user, classification, chunk) so re-flagging refreshes the reason rather than creating duplicates. Aggregated weekly in the admin quality digest and used as training signal for Tier-3 fine-tuning.","operationId":"flag_citation_api_citation_feedback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CitationFeedbackCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CitationFeedbackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/citation-feedback/my":{"get":{"tags":["Citation feedback"],"summary":"Return ids of citations the current user has already flagged.","description":"Lets the frontend render the flag button in its 'already flagged' state for citations the reviewer has previously marked. Returns the list of classification_ids the user has filed feedback on.","operationId":"list_my_flagged_api_citation_feedback_my_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"type":"string","format":"uuid"},"type":"array","title":"Response List My Flagged Api Citation Feedback My Get"}}}}}}},"/api/feedback":{"post":{"tags":["Feedback"],"summary":"Submit free-form product feedback.","description":"Captures qualitative product feedback from the profile menu and forwards it to the internal feedback inbox via MailPace. Includes the submitting user's email and org id in the body so triage can ping the reporter directly without round-tripping through the database.","operationId":"submit_feedback_api_feedback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackSubmission"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/compliance/dashboard":{"get":{"tags":["Compliance Dashboard"],"summary":"Get organisation compliance dashboard","description":"Returns an aggregated compliance overview for the organisation: total systems, risk level distribution, overall compliance score, per-module summaries, and recent activity.","operationId":"get_org_dashboard_api_compliance_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgComplianceDashboard"}}}}}}},"/api/compliance/systems/{system_id}":{"get":{"tags":["Compliance Dashboard"],"summary":"Get per-system compliance detail","description":"Returns compliance status for a specific AI system across all subscribed regulatory modules, with checklist items and scores.","operationId":"get_system_compliance_api_compliance_systems__system_id__get","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemComplianceDashboard"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/compliance/urgent-actions":{"get":{"tags":["Compliance Dashboard"],"summary":"Get urgent compliance actions","description":"Returns the top N incomplete compliance obligations for the organisation, ordered by risk severity. Used to populate the urgent actions panel on the compliance dashboard.","operationId":"get_urgent_actions_api_compliance_urgent_actions_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":1,"description":"Maximum number of items to return.","default":5,"title":"Limit"},"description":"Maximum number of items to return."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UrgentActionItem"},"title":"Response Get Urgent Actions Api Compliance Urgent Actions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/compliance/dossier":{"get":{"tags":["Compliance Dashboard"],"summary":"Generate compliance dossier (PDF/A-1b)","description":"Generates a PDF/A-1b compliance dossier for the organisation covering all registered AI systems: cover page, executive summary, and a per-system breakdown of risk classification, compliance score, checklist progress, evidence inventory, and outstanding obligations. Returns the PDF inline so the browser can render it directly.","operationId":"generate_dossier_api_compliance_dossier_get","parameters":[{"name":"system_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"System Id"}},{"name":"include_deleted","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Deleted"}},{"name":"decommission_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Decommission Date"}},{"name":"lang","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lang"}}],"responses":{"200":{"description":"PDF/A-1b dossier rendered inline.","content":{"application/pdf":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/compliance/checklist-items/{item_id}/evidence/{evidence_id}":{"put":{"tags":["Compliance Dashboard"],"summary":"Attach an evidence file to a checklist item","description":"Records that a document is offered in support of this obligation. Optional and non-gating: it does not mark the obligation complete, and an obligation with no attached evidence is a normal state. The evidence must belong to the same AI system. Idempotent — attaching the same file twice is a no-op.","operationId":"link_checklist_evidence_api_compliance_checklist_items__item_id__evidence__evidence_id__put","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"evidence_id","in":"path","required":true,"schema":{"type":"string","title":"Evidence Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Compliance Dashboard"],"summary":"Detach an evidence file from a checklist item","description":"Removes the link only. The obligation's completion state is left alone — the wrong document may have been attached to a judgement the person still stands behind. The file itself is not deleted.","operationId":"unlink_checklist_evidence_api_compliance_checklist_items__item_id__evidence__evidence_id__delete","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}},{"name":"evidence_id","in":"path","required":true,"schema":{"type":"string","title":"Evidence Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/compliance/checklist-items/{item_id}":{"patch":{"tags":["Compliance Dashboard"],"summary":"Update a checklist item","description":"Mark a compliance obligation complete or incomplete, and/or record in your own words how it is handled. The two are independent: a note never completes an obligation, and completing one never requires a note. Recalculates the module's compliance score. Omit a field to leave it unchanged; send `handling_note: null` to clear the note.","operationId":"toggle_checklist_item_api_compliance_checklist_items__item_id__patch","parameters":[{"name":"item_id","in":"path","required":true,"schema":{"type":"string","title":"Item Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChecklistItemUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChecklistItemResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/cra-scan/leads":{"post":{"tags":["CRA Readiness Scan"],"summary":"Submit a completed CRA Readiness Scan and request the report by email.","description":"Captures a lead from the free CRA Readiness Scan at https://maditon.com/cra-scan and queues the gap report to the address given. Public and unauthenticated; rate-limited to 5 requests per hour per IP address.\n\nThe scan itself is scored in the browser against fixed rules — this endpoint stores the result, it does not recompute it. Gap identifiers are validated against the server's catalogue and the emailed report is rendered from that catalogue, so the request cannot influence the content of the email.\n\nThe response is intentionally opaque: it confirms acceptance and nothing else. It does not reveal whether the address was already known, nor whether the email was successfully dispatched.","operationId":"submit_cra_scan_lead_api_v1_cra_scan_leads_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CraScanLeadCreate"}}},"required":true},"responses":{"202":{"description":"Lead accepted. The report send is best-effort.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CraScanLeadAccepted"}}}},"422":{"description":"Malformed payload — bad email, unknown gap id, oversized answers."},"429":{"description":"Rate limit exceeded for this IP address."}}}},"/api/v1/cra-scan/module-interest":{"post":{"tags":["CRA Readiness Scan"],"summary":"Join the interest list for the planned Maditon CRA module.","description":"Registers interest in the CRA module planned for Maditon. Called from the thank-you step of the free scan at https://maditon.com/cra-scan when the visitor explicitly asks to be notified. Public and unauthenticated; rate-limited to 5 requests per hour per IP address.\n\nThe address is stored on a separate interest list used for exactly one thing: a single announcement email when the module ships. The response is intentionally opaque — it does not reveal whether the address was already on the list.","operationId":"register_cra_module_interest_api_v1_cra_scan_module_interest_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CraModuleInterestCreate"}}},"required":true},"responses":{"202":{"description":"Interest registered (or already registered — indistinguishable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CraModuleInterestAccepted"}}}},"422":{"description":"Malformed payload — bad email."},"429":{"description":"Rate limit exceeded for this IP address."}}}},"/api/v1/ai-act-check/prescreens":{"post":{"tags":["AI Act pre-screen"],"summary":"Store a completed AI Act pre-screen so a new account can claim it.","description":"Captures the anonymous pre-screen from https://maditon.com/ai-act-check. Public and unauthenticated; rate-limited per IP address. The pre-screen is scored in the browser against fixed rules — this endpoint stores the result, it does not recompute it, and nothing in the payload can influence what any account later sees.\n\nNo email address and no name is collected. The submission carries an anonymous browser-generated UUID; an account created later with that UUID claims the answers as the prefill for its first AI system.\n\nThe response is intentionally opaque: acceptance, nothing else.","operationId":"submit_prescreen_api_v1_ai_act_check_prescreens_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrescreenCreate"}}},"required":true},"responses":{"202":{"description":"Pre-screen accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrescreenAccepted"}}}},"422":{"description":"Malformed payload."},"429":{"description":"Rate limit exceeded for this IP address."}}}},"/api/v1/ai-act-check/prescreens/{anonymous_id}/claim":{"post":{"tags":["AI Act pre-screen"],"summary":"Claim a pre-screen for the signed-in user and return its answers.","description":"Ties the pre-screen with this anonymous id to the calling user and returns the answers so the app can prefill their first AI system. Idempotent for the same user (a refresh re-returns the content); a pre-screen claimed by a different user is indistinguishable from one that never existed.","operationId":"claim_prescreen_api_v1_ai_act_check_prescreens__anonymous_id__claim_post","parameters":[{"name":"anonymous_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Anonymous Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PrescreenClaimResult"}}}},"404":{"description":"No such pre-screen, or it belongs to someone else."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/legal/status":{"get":{"tags":["Legal"],"summary":"Current legal documents and what this user still owes.","description":"Returns the current version of each document a customer accepts or acknowledges, and which of them the calling user has not yet accepted at that version. The client uses `outstanding` to decide whether to prompt; an empty list means nothing is owed.","operationId":"get_status_api_legal_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalStatus"}}}}}}},"/api/legal/acceptances":{"post":{"tags":["Legal"],"summary":"Record acceptance of one or more legal documents.","description":"Records an affirmative acceptance against a named document version. Idempotent per (organisation, user, document, version): re-submitting the same acceptance returns the same state rather than creating a second row.\n\nA superseded version is rejected rather than stored — a record naming a text the customer was not shown is worse than no record at all.","operationId":"create_acceptances_api_legal_acceptances_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptanceBatch"}}},"required":true},"responses":{"201":{"description":"Recorded. Returns the updated status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LegalStatus"}}}},"422":{"description":"Unknown document, superseded version, or missing authority."}}}},"/api/evidence":{"post":{"tags":["Evidence"],"summary":"Upload an evidence file","description":"Upload a file as evidence for an AI system compliance dossier. Accepted types: PDF, DOCX, XLSX, PNG, JPG, TXT. Maximum 50 MB.","operationId":"upload_evidence_api_evidence_post","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_upload_evidence_api_evidence_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvidenceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Evidence"],"summary":"List evidence for an AI system","operationId":"list_evidence_api_evidence_get","parameters":[{"name":"ai_system_id","in":"query","required":true,"schema":{"type":"string","title":"Ai System Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EvidenceResponse"},"title":"Response List Evidence Api Evidence Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/evidence/systems/{system_id}/download-all":{"get":{"tags":["Evidence"],"summary":"Download all evidence files for a system as a ZIP archive","operationId":"download_all_evidence_api_evidence_systems__system_id__download_all_get","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/evidence/{evidence_id}/download":{"get":{"tags":["Evidence"],"summary":"Download an evidence file","operationId":"download_evidence_api_evidence__evidence_id__download_get","parameters":[{"name":"evidence_id","in":"path","required":true,"schema":{"type":"string","title":"Evidence Id"}}],"responses":{"200":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/evidence/{evidence_id}":{"patch":{"tags":["Evidence"],"summary":"Update evidence metadata","description":"Update the title and/or type of an evidence file. The file itself cannot be replaced.","operationId":"update_evidence_api_evidence__evidence_id__patch","parameters":[{"name":"evidence_id","in":"path","required":true,"schema":{"type":"string","title":"Evidence Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvidenceUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EvidenceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Evidence"],"summary":"Delete an evidence file","description":"Soft-deletes the metadata row and removes the file from storage.","operationId":"delete_evidence_api_evidence__evidence_id__delete","parameters":[{"name":"evidence_id","in":"path","required":true,"schema":{"type":"string","title":"Evidence Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/audit":{"get":{"tags":["Audit"],"summary":"List audit log entries for the organisation","description":"Returns a paginated, filtered audit trail for the organisation. Supports filtering by AI system, action category, and date range.","operationId":"list_org_audit_api_audit_get","parameters":[{"name":"ai_system_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ai System Id"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"from_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"From Date"}},{"name":"to_date","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"To Date"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLogListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/audit/systems/{system_id}":{"get":{"tags":["Audit"],"summary":"List audit log entries for an AI system","description":"Returns up to 50 recent audit entries scoped to the given AI system, ordered newest first. Requires the system to belong to the caller's organisation.","operationId":"list_system_audit_api_audit_systems__system_id__get","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AuditLogEntryResponse"},"title":"Response List System Audit Api Audit Systems  System Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modules":{"get":{"tags":["Regulatory Modules"],"summary":"List all regulatory modules","description":"Returns all regulatory modules available in Maditon. This endpoint is public — no authentication required — to support discoverability by AI agents.","operationId":"list_modules_api_modules_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/RegulatoryModuleResponse"},"type":"array","title":"Response List Modules Api Modules Get"}}}}}}},"/api/modules/{code}":{"get":{"tags":["Regulatory Modules"],"summary":"Get a regulatory module by code","description":"Retrieve a specific regulatory module by its unique code.","operationId":"get_module_api_modules__code__get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegulatoryModuleResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modules/{code}/notify":{"post":{"tags":["Regulatory Modules"],"summary":"Request notification when a module ships","description":"Records a demand signal for a module that is not yet ``active``. Idempotent — repeated clicks by the same user return the existing record rather than creating duplicates.","operationId":"notify_me_api_modules__code__notify_post","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleNotifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/modules/notifications/mine":{"get":{"tags":["Regulatory Modules"],"summary":"List the current user's 'Notify me' subscriptions","description":"Returns the set of modules the authenticated user has signed up to be notified about. Used by the Modules page to flip the CTA from 'Notify me' to 'You'll be notified'.","operationId":"list_my_notifications_api_modules_notifications_mine_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ModuleNotifyResponse"},"type":"array","title":"Response List My Notifications Api Modules Notifications Mine Get"}}}}}}},"/api/modules/{code}/articles":{"get":{"tags":["Regulatory Modules"],"summary":"List articles for a regulatory module","description":"Returns all articles and annexes for a specific regulation. Public endpoint for agent discoverability.","operationId":"list_articles_api_modules__code__articles_get","parameters":[{"name":"code","in":"path","required":true,"schema":{"type":"string","title":"Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/RegulatoryArticleResponse"},"title":"Response List Articles Api Modules  Code  Articles Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/vat/check":{"post":{"tags":["Billing"],"summary":"Validate an EU VAT identification against VIES","description":"Used by the onboarding form to give the user immediate feedback. VIES is unreliable in practice — when it times out we return ``status='unknown'`` so the form can let the value through; Stripe Tax re-validates at checkout.","operationId":"check_vat_api_billing_vat_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VatCheckRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VatCheckResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/checkout":{"post":{"tags":["Billing"],"summary":"Create a checkout session","description":"Creates a Stripe Checkout session for plan upgrade with optional add-on modules or bundles. Supports monthly and annual billing.","operationId":"create_checkout_api_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/subscriptions":{"get":{"tags":["Billing"],"summary":"List active module subscriptions","description":"Returns all active module subscriptions for the organisation.","operationId":"list_subscriptions_api_billing_subscriptions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ModuleSubscriptionResponse"},"type":"array","title":"Response List Subscriptions Api Billing Subscriptions Get"}}}}}}},"/api/billing/summary":{"get":{"tags":["Billing"],"summary":"Get billing summary","description":"Returns the organisation's current billing status.","operationId":"get_billing_summary_api_billing_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionSummary"}}}}}}},"/api/billing/invoices":{"get":{"tags":["Billing"],"summary":"List the org's Stripe invoices","description":"Powers Settings → Billing → Invoices. Returns the most recent invoices for the org's Stripe customer, newest first. Empty list for trial orgs without a charged invoice yet, and for government accounts (no Stripe customer at all). Live read from Stripe — not cached locally. ``limit`` is bounded at 100; the Settings → Billing inline table defaults to 10 and the 'Show all' dialog asks for 100.","operationId":"list_invoices_api_billing_invoices_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/plan-change-preview":{"post":{"tags":["Billing"],"summary":"Preview a plan change","description":"Returns the set of currently-included modules that lose their inclusion under the target plan. The plan-change modal calls this before submitting ``/change-plan`` so the user can choose, per module, whether to keep it as a paid add-on or cancel it at phase transition. Empty list means no module decisions are required.","operationId":"plan_change_preview_api_billing_plan_change_preview_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanChangePreviewRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanChangePreview"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/change-plan":{"post":{"tags":["Billing"],"summary":"Change plan","description":"Switches the organisation between Starter and Pro, monthly and annual. Upgrades and same-tier monthly → annual swaps apply immediately with proration. Downgrades and same-tier annual → monthly swaps schedule the switch at the end of the current paid period via Stripe SubscriptionSchedules. Replaces the Stripe Customer Portal flow for plan changes; the Portal remains for payment-method updates.","operationId":"change_plan_api_billing_change_plan_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePlanRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/cancel-scheduled-change":{"post":{"tags":["Billing"],"summary":"Cancel a pending plan change (regret button)","description":"Releases the Stripe SubscriptionSchedule queued for this org, leaving the underlying subscription on its current plan. No charge, no proration. No-op-with-error when nothing is queued.","operationId":"cancel_scheduled_change_api_billing_cancel_scheduled_change_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionSummary"}}}}}}},"/api/billing/modules/{module_code}/subscribe":{"post":{"tags":["Billing"],"summary":"Subscribe to a module","description":"Add a module to the organisation's subscription.","operationId":"subscribe_to_module_api_billing_modules__module_code__subscribe_post","parameters":[{"name":"module_code","in":"path","required":true,"schema":{"type":"string","title":"Module Code"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModuleSubscriptionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/modules/{module_code}/unsubscribe":{"delete":{"tags":["Billing"],"summary":"Unsubscribe from a module","description":"Remove a module from the subscription. Cannot unsubscribe bundle items individually.","operationId":"unsubscribe_from_module_api_billing_modules__module_code__unsubscribe_delete","parameters":[{"name":"module_code","in":"path","required":true,"schema":{"type":"string","title":"Module Code"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/portal":{"post":{"tags":["Billing"],"summary":"Open Stripe Customer Portal (payment method update only)","description":"Returns a URL to the Stripe Customer Portal pinned to the ``payment_method_update`` flow. Cancellation is handled by Maditon's own in-app dialog (``POST /api/billing/cancel``) so we can capture a reason — the Portal's own cancellation surface is intentionally skipped. The Portal renders in the user's stored language.","operationId":"open_portal_api_billing_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}}}}},"/api/billing/resume":{"post":{"tags":["Billing"],"summary":"Resume a pending cancellation","description":"Reverses a cancel_at_period_end cancellation, keeping the subscription active. No-op if the subscription is not pending cancellation.","operationId":"resume_billing_api_billing_resume_post","responses":{"204":{"description":"Successful Response"}}}},"/api/billing/activate":{"post":{"tags":["Billing"],"summary":"End the free trial now and start the paid subscription","description":"Ends the 14-day trial immediately: Stripe charges the card already on file and billing starts now. The trial-only quota override (1 AI system) lifts at once and the plan's full limits apply. Owner/admin + recent auth required. Returns 400 if the org is not currently in a trial.","operationId":"activate_billing_api_billing_activate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Activate Billing Api Billing Activate Post"}}}}}}},"/api/billing/cancel":{"post":{"tags":["Billing"],"summary":"Cancel subscription at period end","description":"Schedules the subscription to cancel when the current period ends. During the 14-day trial, this prevents conversion — no charge will be made. The customer keeps full access until the period end. Captures a reason code (and optional free-text reason) for the ``subscription_cancellations`` log, and notifies ``contact@maditon.com`` so the team sees every cancellation.","operationId":"cancel_billing_api_billing_cancel_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancellationRequest"}}},"required":true},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organisations/me":{"get":{"tags":["Organisations"],"summary":"Get current organisation","description":"Returns the organisation profile for the authenticated user.","operationId":"get_my_org_api_organisations_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationResponse"}}}}}},"patch":{"tags":["Organisations"],"summary":"Update current organisation","description":"Partially updates the organisation profile. Only fields that are present in the request body are written.","operationId":"update_my_org_api_organisations_me_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organisations/me/slug":{"patch":{"tags":["Organisations"],"summary":"Change the public transparency slug","description":"Feature-flagged. Disabled at MVP — the slug-history table and 301 redirects are not ready yet. Customers who need a slug change contact support until this endpoint is unlocked.","operationId":"update_my_slug_api_organisations_me_slug_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationSlugUpdate"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organisations/me/slug/request-change":{"post":{"tags":["Organisations"],"summary":"Request a slug change (manual review)","description":"Until self-serve slug editing ships, customers file a request from Settings → Transparency. We validate the desired slug format, log the request to the audit trail, and email ``admin@maditon.com`` so the change can be applied manually.","operationId":"request_slug_change_api_organisations_me_slug_request_change_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationSlugChangeRequest"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Request Slug Change Api Organisations Me Slug Request Change Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organisations/me/attribution":{"patch":{"tags":["Organisations"],"summary":"Toggle the 'Powered by Maditon' footer","description":"Pro, Team, and Government plans may hide the attribution footer. Starter and trial plans must keep it visible.","operationId":"update_my_attribution_api_organisations_me_attribution_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttributionToggleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organisations/me/transparency":{"patch":{"tags":["Organisations"],"summary":"Publish or unpublish the organisation's transparency page","description":"Opts the organisation in or out of its public transparency page (audit F-08). Default is off. Individual systems still have to be published one by one; this only controls whether the page — and the org's sitemap entry — is visible at all.","operationId":"update_my_transparency_publication_api_organisations_me_transparency_patch","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrgTransparencyToggleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organisations/slug/check":{"get":{"tags":["Organisations"],"summary":"Check whether a slug is available","description":"Used by the onboarding form to gate the 'Continue' button. Validates the candidate against the character rules, the reserved list and the current organisation table, returning a suggestion when the candidate is taken.","operationId":"check_slug_api_organisations_slug_check_get","parameters":[{"name":"slug","in":"query","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SlugCheckResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/organisations/me/onboarding":{"post":{"tags":["Organisations"],"summary":"Complete onboarding","description":"Applies the onboarding-form answers to the organisation and flips ``onboarding_completed`` so the app unlocks. Idempotent — if the org is already onboarded, fields are still updated but the flag stays true.","operationId":"complete_my_onboarding_api_organisations_me_onboarding_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingCompleteRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrganisationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/gdpr/export":{"get":{"tags":["GDPR"],"summary":"Export the authenticated user's personal data (GDPR Article 15 + 20)","description":"Returns a machine-readable JSON export of **all personal data** held about the authenticated user: profile, learning history, assessments, knowledge tracking, module-notification signals, and the audit entries attributed to this user.\n\nThis export is scoped to the individual user under GDPR. For the organisation's full data set (the EU Data Act 'right of access' scope covering AI systems, classifications, documents, evidence, etc.), call `/api/data-act/export` instead.\n\nSelf-serve — no admin approval required.","operationId":"export_personal_data_api_gdpr_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/gdpr/delete":{"delete":{"tags":["GDPR"],"summary":"Request account deletion (GDPR Article 17) — 30-day grace","description":"Marks the authenticated user's account for deletion and opens a 30-day grace window. During the window, the user can still log in, edit profile fields (name, email, password) and download their data, but every other mutating endpoint returns HTTP 423 Locked. ``POST /api/gdpr/delete/cancel`` clears the pending state. After the window expires, the ``anonymize_expired_users`` worker (see ``app/jobs/purge.py``) clears all PII columns on the user, replaces the Zitadel subject with a non-routable placeholder, soft-deletes their learning history, and best-effort deletes the upstream user record from every processor we hold an identifier for (Zitadel, Brevo, Stripe). The method is ``DELETE`` per root CLAUDE.md → 'Right to Be Forgotten'. Requires a recent interactive authentication (F-15): a stale session cannot start the destructive workflow without a fresh login.","operationId":"request_deletion_api_gdpr_delete_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Request Deletion Api Gdpr Delete Delete"}}}}}}},"/api/gdpr/delete/cancel":{"post":{"tags":["GDPR"],"summary":"Cancel a pending account deletion","description":"Clears the ``deletion_requested_at`` stamp set by ``POST /api/gdpr/delete`` and unlocks the account for normal editing again. No-op when no deletion is pending.","operationId":"cancel_deletion_api_gdpr_delete_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Cancel Deletion Api Gdpr Delete Cancel Post"}}}}}}},"/api/data-act/export":{"get":{"tags":["Data Act"],"summary":"Export all organisation/account-level data (EU Data Act Article 4)","description":"Returns a machine-readable JSON export of every record tied to the authenticated user's organisation:\n- Organisation profile, billing configuration, quotas, transparency settings\n- All members, including email and name\n- All AI systems, risk classifications, compliance statuses + checklist items\n- Documents, evidence metadata, AI system registrations\n- Learning sessions, user-knowledge records, assessments (all org users)\n- Module subscriptions, notifications, government contracts, campaign redemptions\n- Organisation-group memberships\n- Full audit log for the organisation\n\n**Restricted to organisation owners and admins.** Non-admin members use ``/api/gdpr/export`` for their personal data. The export is self-serve for admins — no separate approval gate — and the request is recorded in the audit log.","operationId":"export_organisation_data_api_data_act_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/data-act/evidence":{"get":{"tags":["Data Act"],"summary":"Download all evidence files as a ZIP archive (EU Data Act Article 4)","description":"Returns every active evidence file the organisation has uploaded, packaged as a ZIP with one folder per AI system. Includes a ``manifest.json`` mapping each file to its Maditon metadata and a ``README.md`` describing the layout.\n\n**Restricted to organisation owners and admins** — evidence may contain confidential business records and personal data attached by other members. Soft-deleted evidence is excluded. The binaries in this archive complement the metadata exported under ``/api/data-act/export``. The request is recorded in the audit log.","operationId":"export_evidence_files_api_data_act_evidence_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/registration/{system_id}":{"get":{"tags":["Registration"],"summary":"Get registration record for a high-risk AI system","description":"Returns the EU AI Act registration record for the specified system. If no record exists yet (first visit), an empty one is created. Includes readiness status showing which required fields are filled.","operationId":"get_registration_api_registration__system_id__get","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["Registration"],"summary":"Create or update a registration record","description":"Upserts the registration record for the specified system. Automatically advances status to 'ready' when all required fields are present. Set status='submitted' to record submission to the EU database. Set status='registered' and provide eu_database_id to confirm registration.","operationId":"upsert_registration_api_registration__system_id__put","parameters":[{"name":"system_id","in":"path","required":true,"schema":{"type":"string","title":"System Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationUpsertRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegistrationResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning/sessions":{"post":{"tags":["Learning"],"summary":"Start a learning session","description":"Creates a new AI tutor session for a given EU AI Act topic. The tutor is contextualised with the relevant article summaries and the user's knowledge level. Quota applies: free plan allows 3 sessions per month.","operationId":"start_session_api_learning_sessions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningSessionCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning/sessions/{session_id}":{"get":{"tags":["Learning"],"summary":"Get a learning session","description":"Returns a learning session and its full conversation history.","operationId":"get_session_api_learning_sessions__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning/sessions/{session_id}/messages":{"post":{"tags":["Learning"],"summary":"Send a message to the AI tutor","description":"Appends the user's message to the session, calls the Mistral-powered EU AI Act tutor, and returns the updated session with the assistant's reply. The tutor is grounded in the topic's article summaries and the user's knowledge level. Answers are kept under 200 words and always cite article numbers.","operationId":"send_message_api_learning_sessions__session_id__messages_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}},{"name":"language","in":"query","required":false,"schema":{"type":"string","description":"Response language (ISO 639-1).","default":"en","title":"Language"},"description":"Response language (ISO 639-1)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageCreate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LearningSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning/assessments":{"post":{"tags":["Learning"],"summary":"Generate a topic quiz","description":"Generates a multiple-choice assessment for a given topic using Mistral. Each question has 4 options and a correct answer index. Difficulty is calibrated to the user's knowledge level.","operationId":"generate_assessment_api_learning_assessments_post","parameters":[{"name":"topic","in":"query","required":true,"schema":{"type":"string","description":"Learning topic key, e.g. 'prohibited'.","title":"Topic"},"description":"Learning topic key, e.g. 'prohibited'."},{"name":"num_questions","in":"query","required":false,"schema":{"type":"integer","maximum":10,"minimum":1,"description":"Number of questions to generate.","default":4,"title":"Num Questions"},"description":"Number of questions to generate."},{"name":"knowledge_level","in":"query","required":false,"schema":{"type":"string","description":"beginner | intermediate | advanced","default":"beginner","title":"Knowledge Level"},"description":"beginner | intermediate | advanced"},{"name":"language","in":"query","required":false,"schema":{"type":"string","description":"ISO 639-1 language code.","default":"en","title":"Language"},"description":"ISO 639-1 language code."}],"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssessmentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning/assessments/{assessment_id}/submit":{"post":{"tags":["Learning"],"summary":"Submit quiz answers","description":"Scores submitted answers and updates the user's knowledge record. Scoring: correct_answers / total_questions * 100. A score >= 80% promotes the user's knowledge level (beginner -> intermediate -> advanced).","operationId":"submit_assessment_api_learning_assessments__assessment_id__submit_post","parameters":[{"name":"assessment_id","in":"path","required":true,"schema":{"type":"string","title":"Assessment Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitAnswers"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssessmentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/learning/knowledge":{"get":{"tags":["Learning"],"summary":"Get user's knowledge progress","description":"Returns a list of per-topic knowledge records for the authenticated user: knowledge level, sessions completed, and last assessment score. Topics not yet started are omitted.","operationId":"get_knowledge_api_learning_knowledge_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UserKnowledgeResponse"},"type":"array","title":"Response Get Knowledge Api Learning Knowledge Get"}}}}}}},"/api/learning/topics/{topic}/articles":{"get":{"tags":["Learning"],"summary":"Get articles for a topic","description":"Returns the EU AI Act article summaries for a given topic key. Used by the frontend to render article content without a separate API call.","operationId":"get_topic_articles_api_learning_topics__topic__articles_get","parameters":[{"name":"topic","in":"path","required":true,"schema":{"type":"string","title":"Topic"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":{"type":"string"}},"title":"Response Get Topic Articles Api Learning Topics  Topic  Articles Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/system-interview/next":{"post":{"tags":["System interview"],"summary":"Run one turn of the AI-led system interview","description":"Drives a plain-language conversation that produces a polished, classifier-ready description of an AI system. On each call the client sends the language, any form fields already filled, and the full conversation history. The service returns either the next question (status=continue) or the final structured draft (status=complete). The interview is ephemeral — no server-side state is kept.","operationId":"run_turn_api_system_interview_next_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewTurnRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewTurnResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/support/chat":{"post":{"tags":["Support"],"summary":"One turn of the in-product support chat","description":"Answers a user's free-form question about how to use the Maditon app. The assistant is grounded in a description of Maditon's features; it never interprets the EU AI Act itself (those questions are redirected to the Learn section). The chat is ephemeral — no server-side state is kept.","operationId":"chat_api_support_chat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportChatRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupportChatResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/transparency/sitemap":{"get":{"tags":["Transparency"],"summary":"List public transparency slugs for sitemap generation","description":"Returns the slug and last-updated timestamp of every organisation that has opted in to its transparency page AND has at least one explicitly published, human-accepted system. Excludes soft-deleted, unpublished, and empty organisations, so the sitemap never enumerates the customer roster.","operationId":"list_slugs_api_transparency_sitemap_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/TransparencySitemapEntry"},"type":"array","title":"Response List Slugs Api Transparency Sitemap Get"}}}}}}},"/api/transparency/{slug}":{"get":{"tags":["Transparency"],"summary":"Public transparency page payload","description":"Fetches the public transparency page for an organisation by slug. Unauthenticated. Rate-limited. Starter/trial orgs see the 'Powered by Maditon' footer as visible regardless of the stored flag.","operationId":"get_transparency_page_api_transparency__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransparencyPageResponse"}}}},"404":{"description":"Slug not found."},"410":{"description":"Organisation was removed and the 30-day grace window has expired."},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/risk-engine-notice/active":{"get":{"tags":["Risk engine notice"],"summary":"Get the active notice for the current user","description":"Returns the currently active risk-engine notice **iff** the authenticated user has not yet dismissed it. Returns 204 No Content when there is nothing to show — either no notice is active, or the user already dismissed it. The frontend uses this on every page-mount via SWR; no notice = no banner.","operationId":"get_active_notice_api_risk_engine_notice_active_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/ActiveNoticeResponse"},{"type":"null"}],"title":"Response Get Active Notice Api Risk Engine Notice Active Get"}}}},"204":{"description":"No active notice to surface."}}}},"/api/risk-engine-notice/{notice_id}/dismiss":{"post":{"tags":["Risk engine notice"],"summary":"Dismiss the active notice for the current user","description":"Records that this user dismissed this notice. Idempotent — calling twice for the same ``(user, notice)`` pair is a no-op. The next ``GET /active`` will return 204 for this user until an admin publishes a new notice.","operationId":"dismiss_notice_api_risk_engine_notice__notice_id__dismiss_post","parameters":[{"name":"notice_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Notice Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AISystemCreate":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Human-readable name of the AI system.","examples":["GitHub Copilot","HubSpot Predictive Lead Scoring"]},"description":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Description","description":"What this AI system does and how it is used.","examples":["AI-powered code completion for software development"]},"provider_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Provider Name","description":"Company or team that built/provides the AI system.","examples":["GitHub (Microsoft)","Internal"]},"purpose":{"anyOf":[{"type":"string","maxLength":5000},{"type":"null"}],"title":"Purpose","description":"Business purpose and use case for this AI system.","examples":["Assists developers by suggesting code completions"]},"domain":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Domain","description":"Business domain where this system operates.","examples":["engineering","hr","finance","marketing","customer_support"]},"role":{"type":"string","title":"Role","description":"Organisation's role: 'provider' (builds it), 'deployer' (uses it), or 'both'.","default":"deployer","examples":["deployer"]},"is_gpai":{"type":"boolean","title":"Is Gpai","description":"Whether this system uses a General-Purpose AI model.","default":false},"data_inputs":{"items":{"type":"string"},"type":"array","title":"Data Inputs","description":"Categories of data this system processes.","examples":[["source_code","user_behaviour","personal_data"]]},"affected_persons":{"items":{"type":"string"},"type":"array","title":"Affected Persons","description":"Groups of people affected by this AI system.","examples":[["employees","customers"]]},"discovered_via":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Discovered Via","description":"How this system was discovered: 'manual', 'saas_scan', 'import'."},"source_tool":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Source Tool","description":"Source tool name if discovered via SaaS scan."}},"type":"object","required":["name"],"title":"AISystemCreate","description":"Register a new AI system for compliance tracking.\n\nAn AI system is any system that uses artificial intelligence,\nas defined in Article 3(1) of the EU AI Act (Regulation 2024/1689)."},"AISystemListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AISystemResponse"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total","description":"Total number of systems matching the query."},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor","description":"Cursor for the next page, or null if this is the last page."}},"type":"object","required":["items","total"],"title":"AISystemListResponse","description":"Paginated list of AI systems."},"AISystemResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"organisation_id":{"type":"string","format":"uuid","title":"Organisation Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"provider_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Name"},"purpose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purpose"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"},"role":{"type":"string","title":"Role"},"is_gpai":{"type":"boolean","title":"Is Gpai"},"data_inputs":{"items":{"type":"string"},"type":"array","title":"Data Inputs"},"affected_persons":{"items":{"type":"string"},"type":"array","title":"Affected Persons"},"risk_level":{"type":"string","title":"Risk Level"},"risk_accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Risk Accepted At"},"risk_accepted_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Risk Accepted By User Id"},"risk_accepted_by_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Risk Accepted By Name"},"is_risk_accepted":{"type":"boolean","title":"Is Risk Accepted","default":false},"transparency_published":{"type":"boolean","title":"Transparency Published","default":false},"public_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Name"},"public_purpose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Public Purpose"},"transparency_published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Transparency Published At"},"assigned_to_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Assigned To User Id"},"discovered_via":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Discovered Via"},"source_tool":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Tool"},"deleted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deleted At"},"compliance_summary":{"anyOf":[{"$ref":"#/components/schemas/ComplianceSummary"},{"type":"null"}]}},"type":"object","required":["id","organisation_id","created_at","updated_at","name","role","is_gpai","risk_level"],"title":"AISystemResponse","description":"Full representation of a registered AI system."},"AISystemUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"provider_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider Name"},"purpose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purpose"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"is_gpai":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Gpai"},"data_inputs":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Data Inputs"},"affected_persons":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Affected Persons"},"assigned_to_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Assigned To User Id"}},"type":"object","title":"AISystemUpdate","description":"Partial update for an AI system. Only provided fields are changed."},"AcceptanceBatch":{"properties":{"acceptances":{"items":{"$ref":"#/components/schemas/AcceptanceRequest"},"type":"array","maxItems":8,"minItems":1,"title":"Acceptances"}},"type":"object","required":["acceptances"],"title":"AcceptanceBatch","description":"Everything accepted in one interaction, recorded together."},"AcceptanceRequest":{"properties":{"kind":{"type":"string","maxLength":32,"title":"Kind"},"version":{"type":"string","maxLength":32,"title":"Version"},"locale":{"type":"string","maxLength":8,"title":"Locale","default":"en"},"authority_representation":{"type":"boolean","title":"Authority Representation","description":"The user represents they may accept on behalf of their organisation. Required for contract documents.","default":false}},"type":"object","required":["kind","version"],"title":"AcceptanceRequest","description":"One document, accepted at a named version."},"ActiveNoticeResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","created_at"],"title":"ActiveNoticeResponse","description":"The active notice for the current user, when one exists and is\nnot yet dismissed. Returned by ``GET /api/risk-engine-notice/active``."},"AffectedModule":{"properties":{"module_id":{"type":"string","title":"Module Id","description":"UUID of the affected module."},"code":{"type":"string","title":"Code","description":"Stable code (e.g. ``eu-ai-act``)."},"name":{"type":"string","title":"Name","description":"Human-readable module name."},"price_monthly_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Price Monthly Cents","description":"Monthly add-on price in EUR cents (null if not priced)."},"price_annual_cents":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Price Annual Cents","description":"Annual add-on price in EUR cents (null if not priced)."}},"type":"object","required":["module_id","code","name"],"title":"AffectedModule","description":"One module that loses its included status under the target plan.\n\nReturned by ``POST /api/billing/plan-change-preview`` so the\nplan-change modal can render a row per module with a keep / cancel\nchoice and an upfront price preview."},"Art501Slot":{"properties":{"applicable":{"type":"boolean","title":"Applicable"},"obviousness_exception_applies":{"type":"boolean","title":"Obviousness Exception Applies"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["applicable","obviousness_exception_applies","reasoning"],"title":"Art501Slot","description":"Article 50(1) with the obviousness exception addressed."},"Art502Slot":{"properties":{"applicable":{"type":"boolean","title":"Applicable"},"assistive_editing_exception_applies":{"type":"boolean","title":"Assistive Editing Exception Applies"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["applicable","assistive_editing_exception_applies","reasoning"],"title":"Art502Slot","description":"Article 50(2) with the assistive-editing exception addressed."},"Art5034Slot":{"properties":{"art_50_3_applicable":{"type":"boolean","title":"Art 50 3 Applicable"},"art_50_4_deepfake_applicable":{"type":"boolean","title":"Art 50 4 Deepfake Applicable"},"art_50_4_public_interest_text_applicable":{"type":"boolean","title":"Art 50 4 Public Interest Text Applicable"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["art_50_3_applicable","art_50_4_deepfake_applicable","art_50_4_public_interest_text_applicable","reasoning"],"title":"Art5034Slot","description":"Articles 50(3) and 50(4), both limbs of 50(4) separately."},"Art5Slot":{"properties":{"a":{"$ref":"#/components/schemas/ProhibitedLetter"},"b":{"$ref":"#/components/schemas/ProhibitedLetter"},"c":{"$ref":"#/components/schemas/ProhibitedLetter"},"d":{"$ref":"#/components/schemas/ProhibitedLetter"},"e":{"$ref":"#/components/schemas/ProhibitedLetter"},"f":{"$ref":"#/components/schemas/ProhibitedLetter"},"g":{"$ref":"#/components/schemas/ProhibitedLetter"},"h":{"$ref":"#/components/schemas/ProhibitedLetter"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["a","b","c","d","e","f","g","h","reasoning"],"title":"Art5Slot","description":"Article 5 — per-letter walk of 5(1)(a)-(h)."},"Art61Slot":{"properties":{"safety_component_of_annex_i_product":{"type":"boolean","title":"Safety Component Of Annex I Product"},"third_party_conformity_assessment_required":{"type":"boolean","title":"Third Party Conformity Assessment Required"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["safety_component_of_annex_i_product","third_party_conformity_assessment_required","reasoning"],"title":"Art61Slot","description":"Article 6(1) — both limbs of the product route."},"Art62Slot":{"properties":{"annex_iii_area":{"type":"string","enum":["biometrics","critical_infrastructure","education","employment","essential_services","law_enforcement","migration","justice_democracy","none"],"title":"Annex Iii Area"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["annex_iii_area","reasoning"],"title":"Art62Slot","description":"Article 6(2) / Annex III."},"Art63Slot":{"properties":{"applicable":{"type":"boolean","title":"Applicable"},"art_49_2_registration_required":{"type":"boolean","title":"Art 49 2 Registration Required"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["applicable","art_49_2_registration_required","reasoning"],"title":"Art63Slot","description":"Article 6(3) — required even (especially) when it does not arise."},"ArticleCitation":{"properties":{"article_number":{"type":"string","title":"Article Number","description":"Article or annex number.","examples":["5(1)(a)","Annex III(1)"]},"title":{"type":"string","title":"Title","description":"Short title of the article.","default":""},"quoted_passage":{"type":"string","title":"Quoted Passage","description":"Exact text quoted from the retrieved regulation passage.","default":""},"conclusion_supported":{"type":"string","title":"Conclusion Supported","description":"Which part of the reasoning this citation supports.","default":""},"chunk_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Chunk Id","description":"Mistral chunk ID for traceability."},"verified":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Verified","description":"Result of the deterministic quote-integrity check: the stored passage was string-matched (normalised) against the corpus chunk it was sliced from, at classification time. None on assessments predating the check."}},"type":"object","required":["article_number"],"title":"ArticleCitation","description":"A grounded citation linking a classification conclusion to regulation text.\n\nUnlike ``RelevantArticle`` (which the LLM generates from memory),\na citation includes the **exact quoted passage** from the retrieved\nlegal text. This makes the assessment auditable: a reviewer can\nverify that the conclusion is supported by the actual regulation."},"AssessmentQuestion":{"properties":{"question":{"type":"string","title":"Question"},"options":{"items":{"type":"string"},"type":"array","title":"Options","description":"Exactly 4 answer options"},"correct_index":{"type":"integer","maximum":3.0,"minimum":0.0,"title":"Correct Index","description":"Index of the correct option (0-3)"}},"type":"object","required":["question","options","correct_index"],"title":"AssessmentQuestion"},"AssessmentReliabilityOut":{"properties":{"tier":{"type":"string","enum":["high","moderate","low"],"title":"Tier","description":"high — unambiguous classification, no open gaps. moderate — classification is probable but something is unresolved; acceptance requires reviewer notes. low — insufficient basis; the system description needs completing and the classification re-running before it can be accepted."},"criteria":{"items":{"$ref":"#/components/schemas/ReliabilityCriterionOut"},"type":"array","title":"Criteria","description":"Every criterion evaluated, met or not, in a stable order."},"met_count":{"type":"integer","title":"Met Count","description":"How many criteria were met."},"total_count":{"type":"integer","title":"Total Count","description":"How many criteria were evaluated."},"blocks_acceptance":{"type":"boolean","title":"Blocks Acceptance","description":"True when the assessment may not be accepted as it stands. Acceptance is always an explicit human act regardless of this flag — nothing in this API approves a classification automatically."},"requires_reviewer_notes":{"type":"boolean","title":"Requires Reviewer Notes","description":"True when acceptance requires the reviewer to record why."}},"type":"object","required":["tier","criteria","met_count","total_count","blocks_acceptance","requires_reviewer_notes"],"title":"AssessmentReliabilityOut","description":"How far this assessment can be relied on, and why.\n\nComputed at read time from what the pipeline observed — never asked\nfrom the model, and never persisted. See\n``app/services/assessment_reliability.py`` for the rubric.\n\nNote for API consumers: this replaces ``confidence`` as the signal to\nact on. ``confidence`` remains on the response for calibration\ntracking, but it is the model's uncalibrated self-report and should\nnot be surfaced to an end user as a percentage."},"AssessmentResponse":{"properties":{"id":{"type":"string","title":"Id"},"topic":{"type":"string","title":"Topic"},"status":{"type":"string","title":"Status"},"questions":{"items":{"$ref":"#/components/schemas/AssessmentQuestion"},"type":"array","title":"Questions"},"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score","description":"Percentage score 0-100 after submission"},"total_questions":{"type":"integer","title":"Total Questions"}},"type":"object","required":["id","topic","status","questions","score","total_questions"],"title":"AssessmentResponse"},"AssessmentResult":{"properties":{"verdict":{"type":"string","enum":["unacceptable","high","limited","minimal"],"title":"Verdict"},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence"},"art_5":{"$ref":"#/components/schemas/Art5Slot"},"art_6_1":{"$ref":"#/components/schemas/Art61Slot"},"art_6_2_annex_iii":{"$ref":"#/components/schemas/Art62Slot"},"art_6_3":{"$ref":"#/components/schemas/Art63Slot"},"art_50_1":{"$ref":"#/components/schemas/Art501Slot"},"art_50_2":{"$ref":"#/components/schemas/Art502Slot"},"art_50_3_50_4":{"$ref":"#/components/schemas/Art5034Slot"},"gpai_role":{"$ref":"#/components/schemas/GpaiRoleSlot"}},"type":"object","required":["verdict","confidence","art_5","art_6_1","art_6_2_annex_iii","art_6_3","art_50_1","art_50_2","art_50_3_50_4","gpai_role"],"title":"AssessmentResult","description":"The validated total output of one classification run.\n\nConstructed only when every slot parsed and every invariant held —\nthe existence of an instance IS the completeness guarantee."},"AttributionToggleRequest":{"properties":{"visible":{"type":"boolean","title":"Visible","description":"True to show the Maditon attribution footer, False to hide."}},"type":"object","required":["visible"],"title":"AttributionToggleRequest","description":"Request body for toggling the 'Powered by Maditon' footer."},"AuditEntry":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"action":{"type":"string","title":"Action"},"entity_type":{"type":"string","title":"Entity Type"},"entity_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Entity Id"},"entity_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Entity Name"},"module_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Module Code"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"User Id"}},"type":"object","required":["id","action","entity_type","created_at"],"title":"AuditEntry","description":"Lightweight audit log entry for dashboard display."},"AuditLogEntryResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"action":{"type":"string","title":"Action"},"entity_type":{"type":"string","title":"Entity Type"},"entity_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Entity Id"},"module_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Module Code"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"user_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Name"},"system_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"System Name"},"metadata":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Metadata"}},"type":"object","required":["id","action","entity_type","created_at"],"title":"AuditLogEntryResponse","description":"A single audit log entry with resolved user and system names."},"AuditLogListResponse":{"properties":{"items":{"items":{"$ref":"#/components/schemas/AuditLogEntryResponse"},"type":"array","title":"Items"},"total":{"type":"integer","title":"Total"},"has_more":{"type":"boolean","title":"Has More"}},"type":"object","required":["items","total","has_more"],"title":"AuditLogListResponse","description":"Paginated list of audit log entries."},"AuthStateResponse":{"properties":{"user_id":{"type":"string","title":"User Id","description":"Internal Maditon user UUID."},"org_id":{"type":"string","title":"Org Id","description":"Internal Maditon organisation UUID."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"User's login email, kept in sync with Zitadel."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"User's display name."},"org_name":{"type":"string","title":"Org Name","description":"Organisation's display name."},"role":{"type":"string","title":"Role","description":"Caller's role within the organisation: owner | admin | member. The frontend uses this to gate admin-only controls (e.g. the org-wide Data Act exports, which the backend restricts to owners/admins). Authoritative enforcement stays server-side; this is for UI affordance only.","default":"member"},"plan":{"type":"string","title":"Plan","description":"Current plan code: solo | starter | pro | owner | team."},"billing_mode":{"type":"string","title":"Billing Mode","description":"'card' (Stripe) or 'government'."},"is_subscribed":{"type":"boolean","title":"Is Subscribed","description":"True once the org has a Stripe subscription ID or is on a non-Stripe plan (government, owner). Gates access to the app."},"onboarding_completed":{"type":"boolean","title":"Onboarding Completed","description":"True once the user has finished the onboarding form. The dashboard is hard-blocked until this flips."},"slug":{"type":"string","title":"Slug","description":"Public transparency-page slug."},"language":{"type":"string","title":"Language","description":"User's preferred UI language (ISO 639-1). The auth-guard redirects to ``/{language}/...`` when the URL locale doesn't match, so a user with ``language='en'`` who lands on ``/sv/dashboard`` after login (because their browser auto-negotiated SV) is bounced to ``/en/dashboard``.","default":"en"},"deletion_requested_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deletion Requested At","description":"Set when the user has requested account deletion. The frontend layout renders a global banner while this is non-null so the pending state is visible from every page, not just Settings."},"payment_past_due":{"type":"boolean","title":"Payment Past Due","description":"True while a renewal payment is failing (Stripe ``past_due``). The frontend layout renders a global 'update your card' banner from every page. Access is retained during the retry window — this is a nudge, not a lockout.","default":false}},"type":"object","required":["user_id","org_id","org_name","plan","billing_mode","is_subscribed","onboarding_completed","slug"],"title":"AuthStateResponse","description":"Shape the frontend middleware consumes on every navigation.\n\nKept deliberately narrow — any field added here is paid for on every\nrequest. Expose richer organisation details through\n``/api/organisations/me`` instead."},"Body_upload_evidence_api_evidence_post":{"properties":{"ai_system_id":{"type":"string","title":"Ai System Id","description":"ID of the AI system this evidence belongs to."},"title":{"type":"string","maxLength":255,"minLength":1,"title":"Title","description":"Evidence title."},"evidence_type":{"type":"string","title":"Evidence Type","description":"Category: technical_documentation, risk_assessment, testing, audit, custom.","default":"custom"},"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File"}},"type":"object","required":["ai_system_id","title","file"],"title":"Body_upload_evidence_api_evidence_post"},"CancellationRequest":{"properties":{"reason_code":{"type":"string","enum":["too_expensive","missing_feature","switching_provider","no_longer_needed","other"],"title":"Reason Code","description":"Why the customer is cancelling."},"reason_text":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Reason Text","description":"Free-text explanation. Required when ``reason_code`` is ``other``. The dialog warns the customer not to include any personal data — the field lands in product-feedback storage."}},"type":"object","required":["reason_code"],"title":"CancellationRequest","description":"Body for ``POST /api/billing/cancel``.\n\nThe dialog presents a fixed set of reason codes plus a free-text\n``reason_text``. ``reason_text`` is mandatory when the reason code\nis ``other`` and optional otherwise (a customer who picks\n\"Too expensive\" doesn't owe us an essay; one who picks \"Other\" does\nso the row is actionable for product feedback)."},"ChangePlanRequest":{"properties":{"plan":{"type":"string","enum":["starter","pro"],"title":"Plan","description":"Target plan code."},"billing_interval":{"type":"string","enum":["monthly","annual"],"title":"Billing Interval","description":"Target billing interval."},"module_decisions":{"items":{"$ref":"#/components/schemas/ModuleDecision"},"type":"array","title":"Module Decisions","description":"Required when the change is a Pro → Starter downgrade and the org has currently-included modules. One entry per affected module — fetch the list via ``POST /api/billing/plan-change-preview`` first."}},"type":"object","required":["plan","billing_interval"],"title":"ChangePlanRequest","description":"Request body for switching plans via our endpoint.\n\nReplaces the previous Stripe-Portal-driven flow. Upgrades fire\nimmediately with proration; downgrades schedule a switch at\nperiod end via Stripe SubscriptionSchedules. See\n``docs/architecture/subscription-schedules.md``."},"ChecklistItemResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"title":{"type":"string","title":"Title"},"key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"article_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Number"},"article_title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Title"},"is_completed":{"type":"boolean","title":"Is Completed"},"completed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Completed At"},"completed_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Completed By User Id"},"evidence":{"items":{"$ref":"#/components/schemas/LinkedEvidenceResponse"},"type":"array","title":"Evidence"},"handling_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Handling Note"},"note_updated_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note Updated At"},"note_updated_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Note Updated By User Id"}},"type":"object","required":["id","title","is_completed"],"title":"ChecklistItemResponse","description":"A single compliance obligation for a system-module pair."},"ChecklistItemUpdate":{"properties":{"is_completed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Completed"},"handling_note":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Handling Note"}},"type":"object","title":"ChecklistItemUpdate","description":"Change a checklist item.\n\nBoth fields are optional and independent. Ticking an obligation does not\nrequire a note, and saving a note does not tick the obligation — they are\nseparate acts, and a note is a description while a tick is somebody taking\na position. Sending neither is a no-op rather than an error."},"CheckoutRequest":{"properties":{"plan":{"type":"string","enum":["starter","pro"],"title":"Plan","description":"Target plan. Government contracts are created via admin endpoints."},"billing_interval":{"type":"string","enum":["monthly","annual"],"title":"Billing Interval","description":"Billing frequency. Annual saves 2 months.","default":"monthly"},"modules":{"items":{"type":"string"},"type":"array","title":"Modules","description":"Add-on module codes to include in the checkout.","examples":[["gdpr","nis2"]]},"bundle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bundle","description":"Bundle code. If set, overrides individual module selection.","examples":["ai-compliance"]},"campaign_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Campaign Code","description":"Promotional campaign code to apply at checkout.","examples":["LAUNCH2026"]},"promo_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Promo Code","description":"The public launch promo code (e.g. LAUNCH50). Resolved server-side against the configured launch coupon — a raw Stripe coupon id here is not accepted.","examples":["LAUNCH50"]}},"type":"object","required":["plan"],"title":"CheckoutRequest","description":"Request body for creating a Stripe checkout session."},"CheckoutResponse":{"properties":{"checkout_url":{"type":"string","title":"Checkout Url","description":"Redirect the user to this URL."}},"type":"object","required":["checkout_url"],"title":"CheckoutResponse","description":"Response containing the Stripe Checkout URL."},"CitationFeedbackCreate":{"properties":{"classification_id":{"type":"string","format":"uuid","title":"Classification Id"},"chunk_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Chunk Id","description":"Mistral / corpus chunk id. May be null for citations filed before chunk_id was wired into the audit trail."},"article_number":{"type":"string","maxLength":100,"title":"Article Number","description":"Citation label as displayed."},"quoted_passage":{"type":"string","maxLength":2000,"title":"Quoted Passage","description":"Excerpt the reviewer was looking at."},"reason":{"type":"string","maxLength":2000,"title":"Reason","description":"Why the reviewer thinks the citation is wrong.","default":""}},"type":"object","required":["classification_id","article_number","quoted_passage"],"title":"CitationFeedbackCreate","description":"Request body for flagging a citation.\n\nSnapshot fields (``article_number``, ``quoted_passage``) are captured\non the client side rather than re-loaded server-side so the digest\ncan render what the reviewer was actually looking at when they\nflagged it, even if the underlying classification or chunk changes."},"CitationFeedbackResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","created_at"],"title":"CitationFeedbackResponse","description":"Returned on successful create."},"ClassificationJobResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"ai_system_id":{"type":"string","format":"uuid","title":"Ai System Id"},"status":{"type":"string","enum":["pending","running","completed","failed"],"title":"Status"},"stage":{"type":"string","enum":["queued","retrieving","classifying","verifying","validating","persisting","completed","failed"],"title":"Stage"},"language":{"type":"string","title":"Language"},"result":{"anyOf":[{"$ref":"#/components/schemas/RiskClassificationResult"},{"type":"null"}],"description":"Populated only when ``status == 'completed'``."},"error_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Message","description":"Populated only when ``status == 'failed'``."},"created_at":{"type":"string","format":"date-time","title":"Created At"},"started_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Started At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"}},"type":"object","required":["id","ai_system_id","status","stage","language","created_at"],"title":"ClassificationJobResponse","description":"Snapshot of an async classification job.\n\nReturned from both the create endpoint (``POST /classify/{id}``,\nwhich fires the background task and returns 202 immediately) and\nthe poll endpoint (``GET /jobs/{id}``). The frontend polls until\n``status`` reaches a terminal value (``completed`` / ``failed``)."},"ComplianceStatusResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"ai_system_id":{"type":"string","format":"uuid","title":"Ai System Id"},"module_id":{"type":"string","format":"uuid","title":"Module Id"},"module_code":{"type":"string","title":"Module Code"},"module_name":{"type":"string","title":"Module Name"},"status":{"type":"string","title":"Status","description":"Compliance status: not_started, in_progress, compliant, non_compliant, not_applicable."},"score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Score","description":"Compliance percentage (0-100)."},"last_assessed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Assessed At"},"checklist_items":{"items":{"$ref":"#/components/schemas/ChecklistItemResponse"},"type":"array","title":"Checklist Items"}},"type":"object","required":["id","ai_system_id","module_id","module_code","module_name","status"],"title":"ComplianceStatusResponse","description":"Compliance status of one AI system for one regulatory module."},"ComplianceSummary":{"properties":{"total_modules":{"type":"integer","title":"Total Modules","description":"Number of regulatory modules this system is tracked against."},"compliant_count":{"type":"integer","title":"Compliant Count","default":0},"in_progress_count":{"type":"integer","title":"In Progress Count","default":0},"not_started_count":{"type":"integer","title":"Not Started Count","default":0},"overall_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overall Score","description":"Weighted compliance score (0-100), or null if unassessed."}},"type":"object","required":["total_modules"],"title":"ComplianceSummary","description":"Inline compliance summary for an AI system."},"CraModuleInterestAccepted":{"properties":{"status":{"type":"string","const":"accepted","title":"Status","default":"accepted"},"message":{"type":"string","title":"Message","default":"You are on the list."}},"type":"object","title":"CraModuleInterestAccepted","description":"Opaque acceptance, mirroring ``CraScanLeadAccepted``.\n\n\"Already on the list\" and \"just added\" are deliberately the same answer —\nanything else is an address-enumeration oracle."},"CraModuleInterestCreate":{"properties":{"email":{"type":"string","maxLength":320,"minLength":5,"pattern":"^[^@\\s;,<>\\\"]{1,64}@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$","title":"Email"},"website":{"type":"string","maxLength":200,"title":"Website","default":""}},"type":"object","required":["email"],"title":"CraModuleInterestCreate","description":"One click on \"put me on the CRA-module interest list\".\n\nSame defensive posture as the lead schema — public, unauthenticated,\nhostile input — but the surface is a single email address plus the\nhoneypot. No scan result travels with it: the interest signal is about\nthe module, not about how the sender scored."},"CraScanLeadAccepted":{"properties":{"status":{"type":"string","const":"accepted","title":"Status","default":"accepted"},"message":{"type":"string","title":"Message","default":"Your report is on its way."}},"type":"object","title":"CraScanLeadAccepted","description":"What the browser gets back.\n\nDeliberately opaque: no lead id, no confirmation of whether the address was\nseen before, no send status. A public endpoint that reports \"we already have\nthis address\" is an address-enumeration oracle, and one that reports the\nMailPace outcome invites retry loops against our sending quota."},"CraScanLeadCreate":{"properties":{"email":{"type":"string","maxLength":320,"minLength":5,"pattern":"^[^@\\s;,<>\\\"]{1,64}@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)+$","title":"Email"},"company":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Company"},"marketing_consent":{"type":"boolean","title":"Marketing Consent","default":false},"applicability":{"type":"string","enum":["in_scope","likely_in_scope","likely_out_of_scope"],"title":"Applicability"},"readiness":{"type":"string","enum":["ready","gaps","critical_gaps"],"title":"Readiness"},"category_tier":{"type":"string","enum":["default","important_class_i","important_class_ii","critical"],"title":"Category Tier","default":"default"},"checks_total":{"type":"integer","maximum":40.0,"minimum":0.0,"title":"Checks Total","default":0},"gap_ids":{"items":{"type":"string"},"type":"array","maxItems":40,"title":"Gap Ids"},"answers":{"additionalProperties":true,"type":"object","title":"Answers"},"utm":{"$ref":"#/components/schemas/UtmParams"},"website":{"type":"string","maxLength":200,"title":"Website","default":""}},"type":"object","required":["email","applicability","readiness"],"title":"CraScanLeadCreate","description":"A completed scan plus the address to send the report to."},"EmailChangeInitiateRequest":{"properties":{"new_email":{"type":"string","maxLength":320,"minLength":3,"title":"New Email","description":"The address to switch to. Zitadel runs the actual format validation and rejects malformed input via its own error codes — we do not duplicate the regex here."}},"type":"object","required":["new_email"],"title":"EmailChangeInitiateRequest","description":"Body for ``POST /me/email-change`` — request a verification email."},"EmailChangeInitiateResponse":{"properties":{"pending_email":{"type":"string","title":"Pending Email","description":"The address Zitadel is now waiting to verify."}},"type":"object","required":["pending_email"],"title":"EmailChangeInitiateResponse","description":"Acknowledges that Zitadel is sending a verification email to the new address."},"EvidenceResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"organisation_id":{"type":"string","format":"uuid","title":"Organisation Id"},"ai_system_id":{"type":"string","format":"uuid","title":"Ai System Id"},"created_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Created By User Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"title":{"type":"string","title":"Title"},"evidence_type":{"type":"string","title":"Evidence Type"},"file_name":{"type":"string","title":"File Name"},"file_size":{"type":"integer","title":"File Size","description":"File size in bytes."},"mime_type":{"type":"string","title":"Mime Type"}},"type":"object","required":["id","organisation_id","ai_system_id","created_at","updated_at","title","evidence_type","file_name","file_size","mime_type"],"title":"EvidenceResponse","description":"Metadata for an uploaded evidence file."},"EvidenceUpdate":{"properties":{"title":{"anyOf":[{"type":"string","maxLength":255,"minLength":1},{"type":"null"}],"title":"Title"},"evidence_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Evidence Type"}},"type":"object","title":"EvidenceUpdate","description":"Partial update for evidence metadata. File is not replaceable via this endpoint."},"FeedbackSubmission":{"properties":{"message":{"type":"string","maxLength":4000,"minLength":4,"title":"Message"},"locale":{"anyOf":[{"type":"string","maxLength":8},{"type":"null"}],"title":"Locale","description":"Active UI locale at submission time, for triage routing."}},"type":"object","required":["message"],"title":"FeedbackSubmission","description":"Free-form product feedback from the profile menu."},"GpaiRoleSlot":{"properties":{"uses_gpai_model":{"type":"boolean","title":"Uses Gpai Model"},"downstream_provider_of_own_system":{"type":"boolean","title":"Downstream Provider Of Own System"},"fine_tuning_computationally_significant":{"type":"boolean","title":"Fine Tuning Computationally Significant"},"reasoning":{"type":"string","minLength":1,"title":"Reasoning"}},"type":"object","required":["uses_gpai_model","downstream_provider_of_own_system","fine_tuning_computationally_significant","reasoning"],"title":"GpaiRoleSlot","description":"The GPAI role split, with the fine-tuning threshold caveat."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"InterviewDraft":{"properties":{"description":{"type":"string","title":"Description","description":"Polished 2-4 sentence description in the user's language."},"purpose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purpose"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"One of: healthcare, finance, law-enforcement, employment, education, infrastructure, other. Null when truly unclear."},"is_gpai":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Gpai","description":"True if the system is built on or is a general-purpose AI model."},"data_inputs":{"items":{"type":"string"},"type":"array","title":"Data Inputs","description":"Zero or more uppercase tokens from the fixed DATA_INPUT_OPTIONS list."},"affected_persons":{"items":{"type":"string"},"type":"array","title":"Affected Persons","description":"Free-text phrases in the user's language."}},"type":"object","required":["description"],"title":"InterviewDraft","description":"Polished structured output from the interview.\n\nFields mirror the new-system form so the frontend can populate the form\ndirectly from this draft."},"InterviewKnownFields":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Description"},"provider_name":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Provider Name"},"purpose":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Purpose"},"domain":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Domain"}},"type":"object","title":"InterviewKnownFields","description":"Fields the user may have already filled in on the new-system form.\n\nThe interviewer must never re-ask for anything provided here. Every\nfield is length-bounded (audit F-25) — these are echoed into the prompt,\nso an unbounded string here is an unbounded prompt."},"InterviewMessage":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role"},"content":{"type":"string","maxLength":4000,"minLength":1,"title":"Content"}},"type":"object","required":["role","content"],"title":"InterviewMessage","description":"One turn in the interview history."},"InterviewTurnRequest":{"properties":{"language":{"type":"string","maxLength":10,"minLength":2,"title":"Language","description":"ISO 639-1 language code for the interview.","default":"en"},"known_fields":{"$ref":"#/components/schemas/InterviewKnownFields"},"history":{"items":{"$ref":"#/components/schemas/InterviewMessage"},"type":"array","maxItems":40,"title":"History","description":"Conversation so far. Empty on the first turn."}},"type":"object","title":"InterviewTurnRequest","description":"Input for one interview turn.\n\nThe client sends the full history plus any form fields already filled.\nThe server returns either the next question or the final draft."},"InterviewTurnResponse":{"properties":{"status":{"type":"string","enum":["continue","complete"],"title":"Status"},"question":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Question","description":"Next question for the user, in their language. Set when status=continue."},"draft":{"anyOf":[{"$ref":"#/components/schemas/InterviewDraft"},{"type":"null"}],"description":"Structured draft ready to populate the form. Set when status=complete."}},"type":"object","required":["status"],"title":"InterviewTurnResponse","description":"Output of one interview turn.\n\nExactly one of `question` or `draft` is populated, keyed by `status`."},"InvoiceListResponse":{"properties":{"invoices":{"items":{"$ref":"#/components/schemas/InvoiceRow"},"type":"array","title":"Invoices","description":"Invoices in reverse chronological order (newest first). Empty list for orgs without a Stripe customer (e.g. fresh trial orgs before first charge, or government accounts)."}},"type":"object","title":"InvoiceListResponse","description":"Response body for ``GET /api/billing/invoices``."},"InvoiceRow":{"properties":{"id":{"type":"string","title":"Id","description":"Stripe invoice ID, e.g. ``in_1abc...``."},"number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Number","description":"Human-readable invoice number assigned by Stripe."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When Stripe created the invoice (UTC)."},"period_start":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Period Start","description":"Start of the billing period covered by the invoice."},"period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Period End","description":"End of the billing period covered by the invoice."},"amount_due_cents":{"type":"integer","title":"Amount Due Cents","description":"Amount due in cents."},"amount_paid_cents":{"type":"integer","title":"Amount Paid Cents","description":"Amount paid in cents."},"currency":{"type":"string","title":"Currency","description":"ISO 4217 currency code, lowercase."},"status":{"type":"string","enum":["open","paid","void","uncollectible"],"title":"Status","description":"Stripe invoice status. ``draft`` invoices are filtered out upstream — they aren't customer-visible."},"hosted_invoice_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hosted Invoice Url","description":"Stripe-hosted page where the customer can pay or view the invoice. ``None`` for void invoices."},"invoice_pdf":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Pdf","description":"Direct PDF download URL. ``None`` until Stripe finalises."}},"type":"object","required":["id","created_at","amount_due_cents","amount_paid_cents","currency","status"],"title":"InvoiceRow","description":"One invoice in the customer-facing billing history.\n\nSerialised straight from Stripe's Invoice object via\n``factory_billing.list_customer_invoices``. Amounts are in the\nsmallest currency unit (cents for EUR) per Stripe convention."},"LearningSessionCreate":{"properties":{"topic":{"type":"string","title":"Topic","description":"Learning topic key, e.g. 'prohibited'"},"language":{"type":"string","title":"Language","description":"ISO 639-1 language code","default":"en"},"knowledge_level":{"type":"string","title":"Knowledge Level","description":"User's self-assessed level: beginner | intermediate | advanced","default":"beginner"}},"type":"object","required":["topic"],"title":"LearningSessionCreate"},"LearningSessionResponse":{"properties":{"id":{"type":"string","title":"Id"},"topic":{"type":"string","title":"Topic"},"status":{"type":"string","title":"Status"},"knowledge_level":{"type":"string","title":"Knowledge Level"},"messages":{"items":{"$ref":"#/components/schemas/MessageResponse"},"type":"array","title":"Messages"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","topic","status","knowledge_level","messages","created_at"],"title":"LearningSessionResponse"},"LegalDocument":{"properties":{"kind":{"type":"string","title":"Kind","description":"terms | dpa | privacy"},"version":{"type":"string","title":"Version","description":"ISO date of the published version, e.g. 2026-08-21"},"requires_authority":{"type":"boolean","title":"Requires Authority","description":"True for contract documents. The accepting user must represent that they can bind their organisation."}},"type":"object","required":["kind","version","requires_authority"],"title":"LegalDocument","description":"A document a customer is asked to accept or acknowledge."},"LegalStatus":{"properties":{"documents":{"items":{"$ref":"#/components/schemas/LegalDocument"},"type":"array","title":"Documents"},"outstanding":{"items":{"type":"string"},"type":"array","title":"Outstanding","description":"Document kinds this user has not accepted at the current version."}},"type":"object","required":["documents","outstanding"],"title":"LegalStatus","description":"Current documents, and which of them this user still owes."},"LinkedEvidenceResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"title":{"type":"string","title":"Title"},"file_name":{"type":"string","title":"File Name"},"linked_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Linked By User Id"}},"type":"object","required":["id","title","file_name"],"title":"LinkedEvidenceResponse","description":"An evidence file a person has offered in support of one obligation.\n\nDeliberately thin: enough to recognise and open the document, and nothing\nthat would let the UI imply the link means the obligation is satisfied. It\nrecords who attached what, not that anything was verified."},"MeResponse":{"properties":{"sub":{"type":"string","title":"Sub"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"org_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Org Id"},"language":{"type":"string","title":"Language","default":"en"},"notify_weekly_digest":{"type":"boolean","title":"Notify Weekly Digest","description":"Whether this user receives the Monday compliance digest.","default":true},"notify_compliance_alerts":{"type":"boolean","title":"Notify Compliance Alerts","description":"Whether this user receives compliance-alert emails about EU AI Act changes affecting their systems' classification.","default":true},"notify_deadline_reminder":{"type":"boolean","title":"Notify Deadline Reminder","description":"Whether this user receives statutory-deadline reminder emails as EU AI Act dates approach.","default":true},"marketing_email_opt_in":{"type":"boolean","title":"Marketing Email Opt In","description":"Whether this user is subscribed to the Maditon newsletter. Drives the Settings → Notifications newsletter toggle and the Brevo newsletter-list membership.","default":false},"deletion_requested_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Deletion Requested At","description":"Timestamp at which the user requested account deletion. Non-null means a 30-day grace window is open — every mutating request outside Settings returns HTTP 423 until ``POST /api/gdpr/delete/cancel`` clears it. Drives the pending-deletion banner in Settings."}},"type":"object","required":["sub","email","name","org_id"],"title":"MeResponse","description":"Public shape of the authenticated user's profile.\n\nKept intentionally small — the frontend pulls richer data\n(organisation, plan, entitlements) from dedicated endpoints."},"MessageCreate":{"properties":{"content":{"type":"string","maxLength":2000,"minLength":1,"title":"Content"}},"type":"object","required":["content"],"title":"MessageCreate"},"MessageResponse":{"properties":{"role":{"type":"string","title":"Role","description":"'user' or 'assistant'"},"content":{"type":"string","title":"Content"},"created_at":{"type":"string","title":"Created At","description":"ISO datetime string"}},"type":"object","required":["role","content","created_at"],"title":"MessageResponse"},"ModuleComplianceSummary":{"properties":{"module_code":{"type":"string","title":"Module Code"},"module_name":{"type":"string","title":"Module Name"},"total_systems":{"type":"integer","title":"Total Systems"},"compliant_count":{"type":"integer","title":"Compliant Count","default":0},"in_progress_count":{"type":"integer","title":"In Progress Count","default":0},"not_started_count":{"type":"integer","title":"Not Started Count","default":0},"average_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Average Score"}},"type":"object","required":["module_code","module_name","total_systems"],"title":"ModuleComplianceSummary","description":"Aggregated compliance for one regulatory module across all systems."},"ModuleDecision":{"properties":{"module_id":{"type":"string","title":"Module Id","description":"UUID of the affected module."},"action":{"type":"string","enum":["keep_as_addon","cancel"],"title":"Action","description":"``keep_as_addon`` adds a Stripe line item for the module so it starts billing as a paid add-on. ``cancel`` marks the module subscription cancelled at phase transition."}},"type":"object","required":["module_id","action"],"title":"ModuleDecision","description":"One per-module choice attached to a Pro → Starter downgrade.\n\nSurfaced by ``POST /api/billing/plan-change-preview`` for every\ncurrently-included module that loses inclusion under the target\nplan, then echoed back on ``POST /api/billing/change-plan`` so\nthe webhook handler can apply the user's intent at phase\ntransition."},"ModuleNotifyResponse":{"properties":{"module_code":{"type":"string","title":"Module Code"},"notified_at":{"type":"string","format":"date-time","title":"Notified At"},"created":{"type":"boolean","title":"Created"}},"type":"object","required":["module_code","notified_at","created"],"title":"ModuleNotifyResponse","description":"Response body for the 'Notify me' action."},"ModuleSubscriptionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"module_code":{"type":"string","title":"Module Code"},"module_name":{"type":"string","title":"Module Name"},"status":{"type":"string","title":"Status","description":"Subscription status: active, cancelled, trial."},"source":{"type":"string","title":"Source","description":"How access was gained: included, addon, bundle, government."},"billing_interval":{"type":"string","title":"Billing Interval","description":"Payment frequency: monthly or annual."},"started_at":{"type":"string","format":"date-time","title":"Started At"},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"}},"type":"object","required":["id","module_code","module_name","status","source","billing_interval","started_at"],"title":"ModuleSubscriptionResponse","description":"An organisation's active subscription to a regulatory module."},"NextAction":{"properties":{"key":{"type":"string","title":"Key","description":"Stable identifier for the action: classify, decommission, decommission_recorded, generate_checklist, begin_module, items_remaining, or all_clear."},"text":{"type":"string","title":"Text","description":"English prose for the action, for API consumers."},"module_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Module Id","description":"The compliance module this action refers to, when it refers to one."},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count","description":"How many items the action refers to, when it counts something."},"urgent":{"type":"boolean","title":"Urgent","description":"Whether this action is legally urgent rather than routine.","default":false}},"type":"object","required":["key","text"],"title":"NextAction","description":"One thing a person can do next on this system — or the fact that there is nothing.\n\nTwo testers asked for opposite halves of the same missing answer: one faced\neleven obligations at once and wanted to know where to start; the other\nfinished and could not tell whether anything remained. Both need the app to\nstate the system's position, so an empty list is never the answer — when\nnothing is outstanding this carries an explicit ``all_clear`` key.\n\n``key`` is stable and drives translation in the UI; ``text`` is the English\nprose kept for the OpenAPI consumers and agents that read this API directly."},"OnboardingCompleteRequest":{"properties":{"name":{"type":"string","maxLength":255,"minLength":1,"title":"Name","description":"Company display name."},"slug":{"anyOf":[{"type":"string","maxLength":40,"minLength":3},{"type":"null"}],"title":"Slug","description":"Optional override of the auto-generated transparency slug. Omit to keep the slug that was auto-generated at bootstrap."},"country":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Country","description":"ISO 3166-1 alpha-2 country code."},"industry":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Industry"},"employee_count":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Employee Count"},"enterprise_size":{"anyOf":[{"type":"string","enum":["micro","small","medium","large"]},{"type":"null"}],"title":"Enterprise Size","description":"Self-declared EU enterprise size (Rec. 2003/361/EC): micro/small/medium/large. Drives which EU AI Act SME reliefs are surfaced."},"has_linked_or_partner_enterprises":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Linked Or Partner Enterprises","description":"Whether the enterprise has linked or partner enterprises. A micro-enterprise only qualifies for the Art. 63 simplified QMS when False."},"vat_number":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Vat Number","description":"Optional EU VAT identification including the country prefix (e.g. 'DE123456789'). Validated against VIES at submit time; stored unvalidated when VIES times out so Stripe Tax can re-validate at checkout."},"language":{"type":"string","enum":["en","sv"],"title":"Language","description":"UI language for this user.","default":"en"},"prescreen_anonymous_id":{"anyOf":[{"type":"string","maxLength":36,"minLength":36},{"type":"null"}],"title":"Prescreen Anonymous Id","description":"Anonymous id of an /ai-act-check pre-screen completed before signup. When present the pre-screen is claimed for this user during onboarding so its answers can prefill the first system. Unknown or foreign ids are ignored, never an error — a stale localStorage value must not block onboarding."},"marketing_email_opt_in":{"type":"boolean","title":"Marketing Email Opt In","description":"Explicit GDPR Art. 6(1)(a) consent for the Maditon newsletter. Defaults False — must be ticked by the user.","default":false}},"type":"object","required":["name"],"title":"OnboardingCompleteRequest","description":"Body posted by the /onboarding form.\n\nSubmitted before the user reaches /pricing — capturing org info upfront\nmeans Stripe Customer creation at checkout has the country and VAT it\nneeds to compute tax correctly."},"OrgComplianceDashboard":{"properties":{"total_systems":{"type":"integer","title":"Total Systems"},"systems_by_risk_level":{"additionalProperties":{"type":"integer"},"type":"object","title":"Systems By Risk Level","description":"Count of systems per risk level.","examples":[{"high":3,"limited":5,"minimal":12,"unclassified":2}]},"overall_compliance_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overall Compliance Score","description":"Organisation-wide compliance score (0-100)."},"action_items_count":{"type":"integer","title":"Action Items Count","description":"Number of incomplete checklist items across all systems."},"modules":{"items":{"$ref":"#/components/schemas/ModuleComplianceSummary"},"type":"array","title":"Modules"},"recent_activity":{"items":{"$ref":"#/components/schemas/AuditEntry"},"type":"array","title":"Recent Activity"}},"type":"object","required":["total_systems","systems_by_risk_level","action_items_count"],"title":"OrgComplianceDashboard","description":"Organisation-level compliance overview.\n\nThis is the primary dashboard view — an aggregate of all AI systems\nand their compliance status across all subscribed regulatory modules."},"OrgTransparencyToggleRequest":{"properties":{"published":{"type":"boolean","title":"Published","description":"True to publish the org's transparency page, false to take it dark."}},"type":"object","required":["published"],"title":"OrgTransparencyToggleRequest","description":"Request body for opting an organisation in or out of its public page."},"OrganisationResponse":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry"},"employee_count":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Count"},"enterprise_size":{"anyOf":[{"type":"string","enum":["micro","small","medium","large"]},{"type":"null"}],"title":"Enterprise Size","description":"Self-declared EU enterprise size (Rec. 2003/361/EC)."},"has_linked_or_partner_enterprises":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Linked Or Partner Enterprises","description":"Whether the enterprise has linked/partner enterprises (Art. 63 test)."},"plan":{"type":"string","title":"Plan"},"billing_mode":{"type":"string","title":"Billing Mode"},"max_systems":{"type":"integer","title":"Max Systems"},"max_users":{"type":"integer","title":"Max Users","description":"Maximum seats on the current plan. -1 means unlimited.","default":1},"slug":{"type":"string","title":"Slug","description":"URL slug for the public transparency page — maditon.com/transparency/<slug>."},"transparency_url":{"type":"string","title":"Transparency Url","description":"Absolute URL of the organisation's public transparency page."},"attribution_visible":{"type":"boolean","title":"Attribution Visible","description":"When true, the transparency page shows a 'Powered by Maditon' footer. Starter/trial orgs cannot turn this off.","default":true},"transparency_published":{"type":"boolean","title":"Transparency Published","description":"Whether the org has opted in to its public transparency page (F-08). Default off — the page is dark until an admin publishes it.","default":false},"vat_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vat Number","description":"EU VAT identification including the country prefix."},"vat_validated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Vat Validated At","description":"Timestamp of the last successful VIES validation. Null when VIES has never confirmed the current value."}},"type":"object","required":["id","name","country","industry","employee_count","plan","billing_mode","max_systems","slug","transparency_url"],"title":"OrganisationResponse","description":"Organisation details returned to the frontend."},"OrganisationSlugChangeRequest":{"properties":{"new_slug":{"type":"string","maxLength":40,"minLength":3,"title":"New Slug","description":"The slug the customer would like to switch to."},"reason":{"type":"string","maxLength":2000,"minLength":1,"title":"Reason","description":"Why they want the change — pasted into the admin email body."}},"type":"object","required":["new_slug","reason"],"title":"OrganisationSlugChangeRequest","description":"Customer-initiated slug change request.\n\nSelf-serve slug editing is still feature-flagged off (see\n``OrganisationSlugUpdate``) because the slug-history + 301 redirect\nmachinery isn't in production yet. In the meantime, customers can\nfile a request from Settings → Transparency: we capture the desired\nslug and reason, email ``admin@maditon.com``, and apply the change\nmanually after a quick sanity check."},"OrganisationSlugUpdate":{"properties":{"slug":{"type":"string","maxLength":40,"minLength":3,"title":"Slug"}},"type":"object","required":["slug"],"title":"OrganisationSlugUpdate","description":"Request body for changing an organisation's slug.\n\nMVP: feature-flagged off. Slug edits require support until the\nslug-history table ships in a later release."},"OrganisationUpdateRequest":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Name"},"country":{"anyOf":[{"type":"string","maxLength":2,"minLength":2},{"type":"null"}],"title":"Country","description":"ISO 3166-1 alpha-2 country code."},"industry":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Industry"},"employee_count":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Employee Count","description":"Employee band, e.g. '1-10', '11-50', '51-200', '201-500', '500+'."},"enterprise_size":{"anyOf":[{"type":"string","enum":["micro","small","medium","large"]},{"type":"null"}],"title":"Enterprise Size","description":"Self-declared EU enterprise size (Rec. 2003/361/EC)."},"has_linked_or_partner_enterprises":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Linked Or Partner Enterprises","description":"Whether the enterprise has linked/partner enterprises (Art. 63 test)."},"vat_number":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"Vat Number","description":"EU VAT identification including the country prefix (e.g. 'DE123456789'). Empty string or null clears the value. When changed, re-validated against VIES and the linked Stripe customer's tax IDs are synced."}},"type":"object","title":"OrganisationUpdateRequest","description":"Partial update — only provided fields are written."},"PasswordChangeRequest":{"properties":{"current_password":{"type":"string","maxLength":512,"minLength":1,"title":"Current Password"},"new_password":{"type":"string","maxLength":512,"minLength":1,"title":"New Password"}},"type":"object","required":["current_password","new_password"],"title":"PasswordChangeRequest","description":"Body for ``POST /me/password`` — current + new password.\n\nNo client-side strength check beyond a non-empty length; Zitadel\nenforces the org password policy (12+ chars, breach check, etc.)\nand is the sole authority on what counts as \"strong enough\". We\nsurface its rejection codes to the user verbatim."},"PlanChangePreview":{"properties":{"affected_modules":{"items":{"$ref":"#/components/schemas/AffectedModule"},"type":"array","title":"Affected Modules"}},"type":"object","title":"PlanChangePreview","description":"Response body — modules that need a decision before the\ndowngrade can proceed. Empty list means the customer can confirm\nwithout additional input."},"PlanChangePreviewRequest":{"properties":{"plan":{"type":"string","enum":["starter","pro"],"title":"Plan","description":"Target plan code."},"billing_interval":{"type":"string","enum":["monthly","annual"],"title":"Billing Interval","description":"Target billing interval."}},"type":"object","required":["plan","billing_interval"],"title":"PlanChangePreviewRequest","description":"Request body for fetching the plan-change preview."},"PrescreenAccepted":{"properties":{"status":{"type":"string","title":"Status","default":"accepted"}},"type":"object","title":"PrescreenAccepted","description":"Deliberately opaque: acceptance, nothing else."},"PrescreenAttribution":{"properties":{"utm_source":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Campaign"},"utm_term":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Term"},"utm_content":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Content"},"gclid":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Gclid"},"landing_path":{"anyOf":[{"type":"string","maxLength":300},{"type":"null"}],"title":"Landing Path"},"referrer":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Referrer"},"first_seen_at":{"anyOf":[{"type":"string","maxLength":40},{"type":"null"}],"title":"First Seen At"}},"type":"object","title":"PrescreenAttribution","description":"Campaign attribution captured on first landing, held until submission."},"PrescreenClaimResult":{"properties":{"anonymous_id":{"type":"string","title":"Anonymous Id"},"variant":{"type":"string","title":"Variant"},"answers":{"additionalProperties":true,"type":"object","title":"Answers"},"result":{"additionalProperties":true,"type":"object","title":"Result"},"already_claimed":{"type":"boolean","title":"Already Claimed","description":"True when this pre-screen was already claimed by the same user — a page refresh, not an error. Claims by a different user 404."}},"type":"object","required":["anonymous_id","variant","answers","result","already_claimed"],"title":"PrescreenClaimResult","description":"What the app needs to prefill the first system after signup."},"PrescreenCreate":{"properties":{"anonymous_id":{"type":"string","maxLength":36,"minLength":36,"title":"Anonymous Id"},"variant":{"type":"string","pattern":"^(organic|ads)$","title":"Variant","default":"organic"},"answers":{"additionalProperties":true,"type":"object","title":"Answers"},"result":{"additionalProperties":true,"type":"object","title":"Result"},"attribution":{"$ref":"#/components/schemas/PrescreenAttribution"},"website":{"type":"string","maxLength":200,"title":"Website","default":""}},"type":"object","required":["anonymous_id"],"title":"PrescreenCreate","description":"A completed anonymous pre-screen, posted from the result screen."},"ProhibitedLetter":{"properties":{"matches":{"type":"boolean","title":"Matches"},"reason":{"type":"string","minLength":1,"title":"Reason"}},"type":"object","required":["matches","reason"],"title":"ProhibitedLetter","description":"One letter of the Art. 5(1) walk."},"RegistrationReadiness":{"properties":{"has_system_name":{"type":"boolean","title":"Has System Name"},"has_provider":{"type":"boolean","title":"Has Provider"},"has_purpose":{"type":"boolean","title":"Has Purpose"},"has_risk_level":{"type":"boolean","title":"Has Risk Level"},"has_high_risk_category":{"type":"boolean","title":"Has High Risk Category"},"has_affected_persons":{"type":"boolean","title":"Has Affected Persons"},"has_countries_deployed":{"type":"boolean","title":"Has Countries Deployed"},"is_ready":{"type":"boolean","title":"Is Ready"}},"type":"object","required":["has_system_name","has_provider","has_purpose","has_risk_level","has_high_risk_category","has_affected_persons","has_countries_deployed","is_ready"],"title":"RegistrationReadiness","description":"Which required EU database fields are already filled for this system."},"RegistrationResponse":{"properties":{"id":{"type":"string","title":"Id"},"ai_system_id":{"type":"string","title":"Ai System Id"},"status":{"type":"string","title":"Status"},"countries_deployed":{"items":{"type":"string"},"type":"array","title":"Countries Deployed"},"eu_representative_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Eu Representative Name"},"eu_representative_contact":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Eu Representative Contact"},"additional_info_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Additional Info Url"},"eu_database_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Eu Database Id"},"submitted_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Submitted At"},"registered_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Registered At"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"readiness":{"$ref":"#/components/schemas/RegistrationReadiness"},"registration_deadline":{"type":"string","title":"Registration Deadline"}},"type":"object","required":["id","ai_system_id","status","countries_deployed","eu_representative_name","eu_representative_contact","additional_info_url","eu_database_id","submitted_at","registered_at","notes","readiness","registration_deadline"],"title":"RegistrationResponse","description":"Full registration record returned from the API."},"RegistrationUpsertRequest":{"properties":{"countries_deployed":{"items":{"type":"string"},"type":"array","title":"Countries Deployed","description":"ISO 3166-1 alpha-2 country codes where the system is deployed."},"eu_representative_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Eu Representative Name","description":"Name of the EU representative (required if provider is outside EU)."},"eu_representative_contact":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Eu Representative Contact","description":"Contact details for the EU representative (email or postal address)."},"additional_info_url":{"anyOf":[{"type":"string","maxLength":2048},{"type":"null"}],"title":"Additional Info Url","description":"URL to additional public information about the system."},"eu_database_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Eu Database Id","description":"Reference ID assigned by the EU database after registration."},"notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Notes","description":"Internal notes — not submitted to the EU database."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Explicit status override. Allowed values: not_started | ready | submitted | registered. If omitted, status is auto-advanced based on readiness."}},"type":"object","title":"RegistrationUpsertRequest","description":"Body for creating or updating a registration record."},"RegulationChunkResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"source":{"type":"string","title":"Source","description":"Regulation source tag, e.g. \"eu_ai_act\"."},"article_number":{"type":"string","title":"Article Number","description":"Article or annex label as stored in the corpus.","examples":["Article 6","ANNEX III"]},"title":{"type":"string","title":"Title"},"content":{"type":"string","title":"Content","description":"Full chunk text — article body plus heading."},"eur_lex_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Eur Lex Url","description":"Deep link into the official EUR-Lex page for this article or annex, or null for sources without a stable EUR-Lex URL. Lets the reviewer verify the quoted text against the authoritative source."}},"type":"object","required":["id","source","article_number","title","content"],"title":"RegulationChunkResponse","description":"Full content of a single regulation chunk."},"RegulatoryArticleResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"module_id":{"type":"string","format":"uuid","title":"Module Id"},"article_number":{"type":"string","title":"Article Number","examples":["6","11(1)(a)","Annex IV"]},"title":{"type":"string","title":"Title"},"summary":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Summary"},"obligation_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Obligation Type","description":"Type of obligation: prohibition, high_risk, transparency, documentation."},"applies_to":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Applies To","description":"Who this applies to: provider, deployer, or both."}},"type":"object","required":["id","module_id","article_number","title"],"title":"RegulatoryArticleResponse","description":"A specific article or annex within a regulation."},"RegulatoryModuleResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"code":{"type":"string","title":"Code","description":"Unique module code.","examples":["eu-ai-act"]},"name":{"type":"string","title":"Name","examples":["EU AI Act"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"regulation_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Regulation Reference","examples":["Regulation (EU) 2024/1689"]},"tier":{"type":"string","title":"Tier","description":"Priority tier: tier_1 through tier_4."},"status":{"type":"string","title":"Status","description":"Lifecycle status: monitoring, coming_soon, beta, active."},"pricing_model":{"type":"string","title":"Pricing Model","description":"How this module is sold: included, addon, bundle_only."},"base_price_eur":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Base Price Eur","description":"Monthly price in cents. Null for included modules."},"annual_price_eur":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Annual Price Eur","description":"Annual price in cents (2 months free)."},"is_visible":{"type":"boolean","title":"Is Visible","description":"Whether to show in the catalog."}},"type":"object","required":["id","code","name","tier","status","pricing_model","is_visible"],"title":"RegulatoryModuleResponse","description":"A regulatory framework available in Maditon."},"RelevantArticle":{"properties":{"article_number":{"type":"string","title":"Article Number","description":"Article or annex number.","examples":["5(1)(a)","Annex III(1)"]},"title":{"type":"string","title":"Title","description":"Short title of the article.","default":""},"relevance":{"type":"string","title":"Relevance","description":"Why this article is relevant to this system.","default":""}},"type":"object","required":["article_number"],"title":"RelevantArticle","description":"A specific EU AI Act article referenced in the classification."},"ReliabilityCriterionOut":{"properties":{"code":{"type":"string","title":"Code","description":"Stable key the client translates.","examples":["no_information_gaps","citations_verified","tier_uncontested"]},"met":{"type":"boolean","title":"Met","description":"Whether this criterion held for this assessment."},"state":{"type":"string","title":"State","description":"What is true right now, as a stable key the client renders — a failing criterion distinguishes 'not_run' from 'below_threshold' from 'claims_flagged' instead of restating the passing assertion in a warning colour.","examples":["verified","claims_flagged","gaps","consensus_disagreed"]},"count":{"type":"integer","title":"Count","description":"The number the failing label needs (open gaps, flagged claims).","default":0}},"type":"object","required":["code","met","state"],"title":"ReliabilityCriterionOut","description":"One observable criterion behind the reliability grade."},"ReviewerNotesUpdateRequest":{"properties":{"reviewer_notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Reviewer Notes","description":"Updated reviewer notes. Pass null to clear."}},"type":"object","title":"ReviewerNotesUpdateRequest","description":"Request body for updating reviewer notes on an accepted classification."},"RiskClassificationAcceptRequest":{"properties":{"reviewer_notes":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Reviewer Notes","description":"Optional reviewer commentary stored alongside the AI reasoning. Appears in the compliance dossier clearly attributed to the reviewer. The AI-generated reasoning is always preserved read-only."}},"type":"object","title":"RiskClassificationAcceptRequest","description":"Optional request body for the accept endpoint."},"RiskClassificationResult":{"properties":{"risk_level":{"type":"string","title":"Risk Level","description":"Classified risk level: unacceptable, high, limited, or minimal."},"confidence":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Confidence","description":"Model confidence in the classification (0.0-1.0)."},"reasoning":{"type":"string","title":"Reasoning","description":"Structured explanation of why this risk level was assigned. Written in the user's language."},"information_gaps":{"items":{"type":"string"},"type":"array","title":"Information Gaps","description":"Specific, actionable items the LLM could not determine from the input and that would refine the classification if clarified. Empty when the description is complete enough for a high-confidence result."},"relevant_articles":{"items":{"$ref":"#/components/schemas/RelevantArticle"},"type":"array","title":"Relevant Articles","description":"EU AI Act articles that informed this classification."},"annex_iii_area":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Annex Iii Area","description":"The Annex III area the system itself operates in, or null when it falls under none of the eight. Serving customers who assess their own Annex III systems does not count."},"art_6_3_applied":{"type":"boolean","title":"Art 6 3 Applied","description":"True only when the conclusion relies on the Art. 6(3) derogation (branch B). Requires annex_iii_area — the derogation is only reachable from inside Annex III.","default":false},"prohibited_practice_match":{"type":"boolean","title":"Prohibited Practice Match","description":"True if the system matches a prohibited practice under Art. 5.","default":false},"prohibited_practice_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prohibited Practice Details","description":"Details of the Art. 5 match, if applicable."},"high_risk_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"High Risk Category","description":"Annex III category if classified as high-risk.","examples":["biometric_identification","critical_infrastructure"]},"transparency_obligations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Transparency Obligations","description":"Art. 50 transparency obligations that apply."},"recommendations":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Recommendations","description":"Recommended next steps for the organisation."},"cited_articles":{"items":{"$ref":"#/components/schemas/ArticleCitation"},"type":"array","title":"Cited Articles","description":"Grounded citations linking conclusions to actual regulation text. Only populated when RAG retrieval is active."},"verifier_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Verifier Confidence","description":"Confidence score from the verifier pass (0.0-1.0)."},"unsupported_claims":{"items":{"type":"string"},"type":"array","title":"Unsupported Claims","description":"Claims in the classification that the verifier could not support."},"requires_expert_review":{"type":"boolean","title":"Requires Expert Review","description":"True when verifier confidence is below threshold or citations are unknown.","default":false},"consensus_model":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consensus Model"},"consensus_risk_level":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consensus Risk Level"},"consensus_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Consensus Confidence"},"consensus_agreement":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Consensus Agreement"},"reliability":{"$ref":"#/components/schemas/AssessmentReliabilityOut","description":"Computed grade replacing the model's self-reported ``confidence``.\n\nDerived from this object's own fields, so it costs no query and\ngrades historical rows identically to fresh ones.","readOnly":true}},"type":"object","required":["risk_level","confidence","reasoning","reliability"],"title":"RiskClassificationResult","description":"Output of the AI-powered risk classification.\n\nThe EU AI Act classifies AI systems into four risk tiers:\nunacceptable (prohibited), high, limited (transparency), and minimal."},"RiskOverrideRequest":{"properties":{"new_risk_level":{"type":"string","title":"New Risk Level","description":"The corrected risk level.","examples":["high","limited"]},"override_reason":{"type":"string","maxLength":2000,"minLength":10,"title":"Override Reason","description":"Mandatory explanation for why the AI classification is incorrect."}},"type":"object","required":["new_risk_level","override_reason"],"title":"RiskOverrideRequest","description":"Human override of an AI-generated risk classification."},"RiskOverrideResponse":{"properties":{"ai_system_id":{"type":"string","format":"uuid","title":"Ai System Id"},"previous_level":{"type":"string","title":"Previous Level"},"new_level":{"type":"string","title":"New Level"},"override_reason":{"type":"string","title":"Override Reason"},"overridden_by_user_id":{"type":"string","format":"uuid","title":"Overridden By User Id"},"overridden_at":{"type":"string","format":"date-time","title":"Overridden At"}},"type":"object","required":["ai_system_id","previous_level","new_level","override_reason","overridden_by_user_id","overridden_at"],"title":"RiskOverrideResponse","description":"Confirmation of a risk level override."},"SlugCheckResponse":{"properties":{"slug":{"type":"string","title":"Slug","description":"The normalised slug that was checked."},"available":{"type":"boolean","title":"Available","description":"True when the slug is free and not on the reserved list."},"suggestion":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Suggestion","description":"A free slug derived from the requested one (adds -2, -3 …). Returned whenever ``available`` is False so the UI can offer a one-click alternative."},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason","description":"Short human-readable explanation when the slug is unavailable."}},"type":"object","required":["slug","available"],"title":"SlugCheckResponse","description":"Result of a /slug/check lookup."},"SmeRelief":{"properties":{"article":{"type":"string","title":"Article","description":"EU AI Act article, e.g. '11', '62', '63', '99(6)'."},"applies_when":{"type":"string","title":"Applies When","description":"Which tier unlocks it: 'sme' (micro/small/medium) or 'micro'."},"title":{"type":"string","title":"Title","description":"Short English title."},"summary":{"type":"string","title":"Summary","description":"Concise English description of the relief."}},"type":"object","required":["article","applies_when","title","summary"],"title":"SmeRelief","description":"One EU AI Act SME / micro-enterprise relief that may apply.\n\nAdvisory only — computed deterministically from the org's declared EU\nenterprise size and the system's role/risk level. It never alters the\nrisk level or removes an obligation. The frontend renders translated copy\nkeyed by ``article``; the English ``title``/``summary`` keep the API and\nData Act export self-describing for agents."},"StoredRiskClassification":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"ai_system_id":{"type":"string","format":"uuid","title":"Ai System Id"},"risk_level":{"type":"string","title":"Risk Level"},"confidence":{"type":"number","title":"Confidence"},"reasoning":{"type":"string","title":"Reasoning"},"information_gaps":{"items":{"type":"string"},"type":"array","title":"Information Gaps"},"relevant_articles":{"items":{"$ref":"#/components/schemas/RelevantArticle"},"type":"array","title":"Relevant Articles"},"prohibited_practice_match":{"type":"boolean","title":"Prohibited Practice Match"},"prohibited_practice_details":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prohibited Practice Details"},"high_risk_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"High Risk Category"},"transparency_obligations":{"items":{"type":"string"},"type":"array","title":"Transparency Obligations"},"recommendations":{"items":{"type":"string"},"type":"array","title":"Recommendations"},"cited_articles":{"items":{"$ref":"#/components/schemas/ArticleCitation"},"type":"array","title":"Cited Articles"},"model_used":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Model Used"},"tokens_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tokens Used"},"language":{"type":"string","title":"Language"},"is_accepted":{"type":"boolean","title":"Is Accepted"},"accepted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Accepted At"},"classified_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Classified By User Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"reviewer_notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewer Notes"},"reviewer_notes_updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Reviewer Notes Updated At"},"reviewer_notes_updated_by_user_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Reviewer Notes Updated By User Id"},"reviewer_notes_author_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reviewer Notes Author Name","description":"Display name (or email fallback) of the reviewer who last wrote or updated the notes. Derived from the users table."},"verifier_confidence":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Verifier Confidence","description":"Confidence score from the verifier pass (0.0-1.0)."},"unsupported_claims":{"items":{"type":"string"},"type":"array","title":"Unsupported Claims","description":"Claims in the classification that the verifier could not support."},"requires_expert_review":{"type":"boolean","title":"Requires Expert Review","description":"True when verifier confidence is below threshold.","default":false},"sme_reliefs":{"items":{"$ref":"#/components/schemas/SmeRelief"},"type":"array","title":"Sme Reliefs","description":"EU AI Act SME / micro-enterprise reliefs that MAY apply given the organisation's self-declared enterprise size and this system's role and risk level. Advisory only, computed at read time (not persisted): it never alters the risk level or removes an obligation."},"reliability":{"$ref":"#/components/schemas/AssessmentReliabilityOut","description":"Computed grade replacing the model's self-reported ``confidence``.\n\nDerived from this object's own fields, so it costs no query and\ngrades historical rows identically to fresh ones.","readOnly":true}},"type":"object","required":["id","ai_system_id","risk_level","confidence","reasoning","relevant_articles","prohibited_practice_match","prohibited_practice_details","high_risk_category","transparency_obligations","recommendations","model_used","tokens_used","language","is_accepted","accepted_at","classified_by_user_id","created_at","reliability"],"title":"StoredRiskClassification","description":"A persisted risk classification record — the full LLM output for a single run."},"SubmitAnswers":{"properties":{"answers":{"items":{"type":"integer"},"type":"array","title":"Answers","description":"Index of selected answer per question (0-3)"}},"type":"object","required":["answers"],"title":"SubmitAnswers"},"SubscriptionSummary":{"properties":{"plan":{"type":"string","title":"Plan","description":"Current plan: starter, pro, team, government, owner, or cancelled (post-cancellation grace state — see ``cancelled_at``)."},"max_systems":{"type":"integer","title":"Max Systems","description":"Effective AI-system limit for the current plan (or trial while it is live). -1 means unlimited (government/owner)."},"systems_used":{"type":"integer","title":"Systems Used","description":"AI systems currently registered (non-deleted) for the org."},"max_assessments_per_month":{"type":"integer","title":"Max Assessments Per Month","description":"Effective monthly risk-assessment limit (trial: lifetime total). -1 means unlimited. Counted by the same function that enforces the quota, so this number and the enforcement cannot disagree.","default":-1},"assessments_used":{"type":"integer","title":"Assessments Used","description":"Risk assessments used in the current period. On the free Solo plan failed runs are excluded — a crash does not consume quota.","default":0},"trial_ends_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Trial Ends At","description":"When the 14-day trial ends. Null for non-trial subscriptions."},"cancelled_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cancelled At","description":"When the subscription truly ended (Stripe webhook flipped the sub to canceled/unpaid). Paired with ``plan = 'cancelled'``. The 30-day Data Act export window runs from this timestamp; after that a scheduled job hands the org to the GDPR delete flow. Null for non-cancelled orgs."},"billing_mode":{"type":"string","title":"Billing Mode","description":"Payment method: card or government."},"billing_interval":{"type":"string","title":"Billing Interval","description":"Current interval: monthly or annual."},"is_government":{"type":"boolean","title":"Is Government","description":"Whether this is a government contract."},"module_count":{"type":"integer","title":"Module Count","description":"Number of active module subscriptions."},"cancel_at_period_end":{"type":"boolean","title":"Cancel At Period End","description":"True if the subscription is set to cancel at the end of the billing period.","default":false},"current_period_end":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Current Period End","description":"When the current billing period ends. Null for non-Stripe or unset."},"scheduled_plan":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled Plan","description":"Plan code the subscription will switch to at the next phase boundary. ``None`` when no change is queued."},"scheduled_billing_interval":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scheduled Billing Interval","description":"Billing interval that takes effect with the scheduled plan."},"scheduled_plan_change_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Scheduled Plan Change At","description":"When Stripe rolls the subscription to the scheduled plan. Drives the 'Plan switches on …' banner copy."}},"type":"object","required":["plan","max_systems","systems_used","billing_mode","billing_interval","is_government","module_count"],"title":"SubscriptionSummary","description":"Current billing status for the organisation."},"SupportChatMessage":{"properties":{"role":{"type":"string","enum":["user","assistant"],"title":"Role"},"content":{"type":"string","maxLength":4000,"minLength":1,"title":"Content"}},"type":"object","required":["role","content"],"title":"SupportChatMessage","description":"One turn in the support chat history."},"SupportChatRequest":{"properties":{"language":{"type":"string","maxLength":10,"minLength":2,"title":"Language","description":"ISO 639-1 language code for the reply.","default":"en"},"history":{"items":{"$ref":"#/components/schemas/SupportChatMessage"},"type":"array","maxItems":40,"title":"History","description":"Conversation so far. Always non-empty; the last turn is a user message."}},"type":"object","title":"SupportChatRequest","description":"Input for one support chat turn.\n\nThe client sends the full conversation history; the last message must\nbe a user turn. The server returns a single markdown-formatted answer."},"SupportChatResponse":{"properties":{"answer":{"type":"string","title":"Answer","description":"Markdown-formatted answer in the user's language."}},"type":"object","required":["answer"],"title":"SupportChatResponse","description":"Output of one support chat turn."},"SystemComplianceDashboard":{"properties":{"ai_system_id":{"type":"string","format":"uuid","title":"Ai System Id"},"ai_system_name":{"type":"string","title":"Ai System Name"},"risk_level":{"type":"string","title":"Risk Level"},"overall_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Overall Score"},"modules":{"items":{"$ref":"#/components/schemas/ComplianceStatusResponse"},"type":"array","title":"Modules"},"next_actions":{"items":{"$ref":"#/components/schemas/NextAction"},"type":"array","title":"Next Actions","description":"What to do next on this system, most important first. Never empty: when nothing is outstanding it carries a single all_clear action."}},"type":"object","required":["ai_system_id","ai_system_name","risk_level"],"title":"SystemComplianceDashboard","description":"Per-system compliance detail view."},"SystemTransparencyRequest":{"properties":{"published":{"type":"boolean","title":"Published","description":"True to publish this system, false to unpublish."},"public_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Public Name","description":"Sanitized public display name. Required when publishing."},"public_purpose":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Public Purpose","description":"Optional sanitized public description of what the system does."}},"type":"object","required":["published"],"title":"SystemTransparencyRequest","description":"Publish or unpublish a single AI system on the public transparency page.\n\n``public_name`` is the sanitized display name shown publicly — required\nwhen publishing, since the public page never serves the internal name\nverbatim (audit F-08). ``public_purpose`` is an optional sanitized blurb.\nWhen ``published`` is false the sanitized fields are ignored."},"TransparencyOrganisation":{"properties":{"name":{"type":"string","title":"Name","description":"Display name of the organisation."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"ISO 3166-1 alpha-2 country code, if provided."},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry","description":"Industry band, if provided."},"last_updated_at":{"type":"string","format":"date-time","title":"Last Updated At","description":"Most recent public-facing change to this page."}},"type":"object","required":["name","last_updated_at"],"title":"TransparencyOrganisation","description":"Public organisation header card."},"TransparencyPageResponse":{"properties":{"organisation":{"$ref":"#/components/schemas/TransparencyOrganisation"},"ai_systems":{"items":{"$ref":"#/components/schemas/TransparencySystem"},"type":"array","title":"Ai Systems"},"attribution_visible":{"type":"boolean","title":"Attribution Visible","description":"When true, the public page shows a 'Powered by Maditon' footer. Starter/trial orgs always see this as true."}},"type":"object","required":["organisation","ai_systems","attribution_visible"],"title":"TransparencyPageResponse","description":"Full response for GET /api/transparency/{slug}."},"TransparencySitemapEntry":{"properties":{"slug":{"type":"string","title":"Slug"},"last_updated_at":{"type":"string","format":"date-time","title":"Last Updated At"}},"type":"object","required":["slug","last_updated_at"],"title":"TransparencySitemapEntry","description":"One row in the sitemap feed — slug + last-modified timestamp."},"TransparencySystem":{"properties":{"name":{"type":"string","title":"Name"},"purpose":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purpose"},"risk_tier":{"type":"string","title":"Risk Tier","description":"One of: unacceptable, high, limited, minimal, unclassified."},"is_human_accepted":{"type":"boolean","title":"Is Human Accepted","description":"True when a human has accepted the AI's risk classification."},"relevant_articles":{"items":{"type":"string"},"type":"array","title":"Relevant Articles","description":"EU AI Act article references, e.g. 'Art. 50(1)'."}},"type":"object","required":["name","risk_tier","is_human_accepted"],"title":"TransparencySystem","description":"A single AI system rendered on the public page.\n\n``is_human_accepted`` signals whether a human has explicitly accepted\nthe AI-generated risk classification — the EU AI Act's human oversight\nprinciple in one field."},"UpdateMeRequest":{"properties":{"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"UI language preference. One of the UserLanguage enum values."},"notify_weekly_digest":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Weekly Digest","description":"Toggle the Monday compliance digest. None = no change."},"notify_compliance_alerts":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Compliance Alerts","description":"Toggle compliance-alert emails. None = no change."},"notify_deadline_reminder":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Notify Deadline Reminder","description":"Toggle statutory-deadline reminder emails. None = no change."},"marketing_email_opt_in":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Marketing Email Opt In","description":"Toggle the newsletter subscription. ``True`` opts in (adds to Brevo newsletter list), ``False`` opts out (removes from list, keeps contact). ``None`` = no change."}},"type":"object","title":"UpdateMeRequest","description":"Patch body for Settings → Account fields.\n\nOnly fields present on the request are written. Passing ``null`` for\na field is rejected — use a different endpoint to clear PII."},"UpdateProfileRequest":{"properties":{"given_name":{"type":"string","maxLength":200,"minLength":1,"title":"Given Name"},"family_name":{"type":"string","maxLength":200,"minLength":1,"title":"Family Name"}},"type":"object","required":["given_name","family_name"],"title":"UpdateProfileRequest","description":"Patch body for the user's display name.\n\nZitadel stores given and family names separately. The frontend\nsplits the name field accordingly; we forward both to Zitadel and\njoin them back into ``users.name`` for our own listings."},"UrgentActionItem":{"properties":{"system_id":{"type":"string","format":"uuid","title":"System Id"},"system_name":{"type":"string","title":"System Name"},"action":{"type":"string","title":"Action","description":"Checklist item title describing what needs to be done."},"article_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Article Number","description":"Raw article number from the regulation, e.g. '9(4)'."},"risk_level":{"type":"string","title":"Risk Level","description":"Risk tier of the system — used for badge styling."}},"type":"object","required":["system_id","system_name","action","risk_level"],"title":"UrgentActionItem","description":"An incomplete compliance obligation requiring immediate attention."},"UserKnowledgeResponse":{"properties":{"topic":{"type":"string","title":"Topic"},"knowledge_level":{"type":"string","title":"Knowledge Level"},"sessions_completed":{"type":"integer","title":"Sessions Completed"},"last_assessment_score":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Assessment Score"}},"type":"object","required":["topic","knowledge_level","sessions_completed","last_assessment_score"],"title":"UserKnowledgeResponse"},"UtmParams":{"properties":{"utm_source":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Source"},"utm_medium":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Medium"},"utm_campaign":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Campaign"},"utm_term":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Term"},"utm_content":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Utm Content"}},"type":"object","title":"UtmParams","description":"Campaign attribution lifted off the landing page URL."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VatCheckRequest":{"properties":{"vat_number":{"type":"string","maxLength":32,"minLength":4,"title":"Vat Number"}},"type":"object","required":["vat_number"],"title":"VatCheckRequest","description":"Request body for the standalone VAT verification endpoint."},"VatCheckResponse":{"properties":{"status":{"type":"string","enum":["valid","invalid","unknown"],"title":"Status","description":"'valid' = VIES confirmed; 'invalid' = VIES rejected; 'unknown' = VIES timed out or returned 5xx — the value can still be saved and Stripe Tax will re-validate at checkout."},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Registered business name returned by VIES, when available."},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country","description":"2-letter country prefix extracted from the VAT number."}},"type":"object","required":["status"],"title":"VatCheckResponse","description":"Tri-state outcome of a VIES check."}}}}