{
  "openapi": "3.0.3",
  "info": {
    "title": "Aletura API",
    "version": "1.0.0",
    "description": "Analyze contracts through the same backend as the web app. All endpoints share the web quotas: a guest gets 1 successful operation per UTC day (per hashed IP), an authenticated Individual account gets 3 per day, and each completed result allows 5 follow-up questions. Failed or cancelled operations are refunded. Requests need the session cookie plus the X-XSRF-TOKEN header obtained from GET /api/auth/session (CSRF protection). Uploaded contracts are masked before any model call and are not persisted unless you explicitly save a result. Import this file into Swagger UI or Postman. See https://aletura.eu/api for a guide.",
    "contact": { "url": "https://aletura.eu/contact" },
    "license": { "name": "Proprietary" }
  },
  "servers": [ { "url": "https://aletura.eu" } ],
  "paths": {
    "/api/openapi.json": {
      "get": {
        "summary": "This OpenAPI (Swagger) specification",
        "operationId": "getOpenApiSpec",
        "tags": ["meta"],
        "security": [],
        "responses": { "200": { "description": "The OpenAPI JSON document", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/api/auth/session": {
      "get": {
        "summary": "Session state, quotas and the CSRF cookie. Call this first: it sets the XSRF-TOKEN cookie and the JSESSIONID session cookie that later calls must send back.",
        "operationId": "getSession",
        "tags": ["auth"],
        "security": [],
        "responses": {
          "200": {
            "description": "Session state",
            "headers": { "Set-Cookie": { "schema": { "type": "string" }, "description": "JSESSIONID and XSRF-TOKEN cookies" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Session" } } }
          }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "summary": "Sign in with email + password (form fields) to get the Individual-account quota (3 operations/day). Guests can skip this and use the guest quota (1/day).",
        "operationId": "login",
        "tags": ["auth"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "application/x-www-form-urlencoded": { "schema": { "type": "object", "required": ["username", "password"], "properties": { "username": { "type": "string", "format": "email" }, "password": { "type": "string", "format": "password" } } } } } },
        "responses": {
          "200": { "description": "Signed in", "content": { "application/json": { "schema": { "type": "object", "properties": { "authenticated": { "type": "boolean" } } } } } },
          "401": { "description": "Invalid credentials", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/auth/register": {
      "post": {
        "summary": "Start Individual-account registration (email only). A 24h-valid verification link is sent by email; the password is chosen when confirming it.",
        "operationId": "register",
        "tags": ["auth"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email"], "properties": { "email": { "type": "string", "format": "email" } } } } } },
        "responses": { "201": { "description": "Registration accepted (always the same response whether or not the address can be registered)" }, "400": { "description": "Registration unavailable (mail not configured)" } }
      }
    },
    "/api/capabilities": {
      "get": { "summary": "Deployment capabilities and current quota state", "operationId": "getCapabilities", "tags": ["meta"], "security": [], "responses": { "200": { "description": "Capabilities", "content": { "application/json": { "schema": { "type": "object" } } } } } }
    },
    "/api/meta": {
      "get": { "summary": "Public product metadata", "operationId": "getMeta", "tags": ["meta"], "security": [], "responses": { "200": { "description": "Metadata", "content": { "application/json": { "schema": { "type": "object" } } } } } }
    },
    "/api/languages/detect": {
      "post": {
        "summary": "Detect the language of uploaded files before analysis (free preflight, not an operation)",
        "operationId": "detectLanguages",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": ["files"], "properties": { "files": { "type": "array", "items": { "type": "string", "format": "binary" } }, "progressive": { "type": "boolean", "default": false, "description": "For a single searchable PDF, allow provisional selectable-text extraction while visible-source verification continues" } } } } } },
        "responses": { "200": { "description": "Detected languages", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "object" } } } } }, "400": { "description": "No files", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } } }
      }
    },
    "/api/reviews": {
      "post": {
        "summary": "Analyze one contract (same operation and quota as the web upload). Returns the structured result JSON. If context confirmation is required, this returns 400 and you must use /api/reviews/prepare + /complete.",
        "operationId": "analyzeContract",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": ["file"], "properties": {
          "file": { "type": "string", "format": "binary", "description": "Contract file: PDF, DOC, DOCX, PPT, PPTX, TXT, MD, RTF, ODT, CSV, HTML, XML or JSON; max 20 MB, max 300 pages" },
          "restoreIdentifiers": { "type": "boolean", "default": false, "description": "Restore masked identifiers in the returned display copy (the model still only sees placeholders)" },
          "confirmedLanguage": { "type": "string", "description": "ISO language code confirmed after /api/languages/detect" },
          "operationId": { "type": "string", "description": "Optional client-supplied operation id for idempotent progress polling" }
        } } } } },
        "responses": {
          "200": { "description": "Structured analysis result (masked by default)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReviewResult" } } } },
          "400": { "description": "Confirmation required or invalid input", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "413": { "description": "File too large", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Daily quota exhausted", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/reviews/prepare": {
      "post": {
        "summary": "Start an analysis with explicit context confirmation (required when the deployment asks for it). Returns a preparation id.",
        "operationId": "prepareReview",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": ["file"], "properties": { "file": { "type": "string", "format": "binary" }, "progressive": { "type": "boolean", "default": false, "description": "Return a clearly provisional searchable-PDF report and verify the visible scan in the background" } } } } } },
        "responses": { "200": { "description": "Preparation started or already confirmed", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/api/reviews/preparations/{preparationId}/complete": {
      "post": {
        "summary": "Complete a confirmed preparation and run the analysis",
        "operationId": "completeReview",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "preparationId", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "perspective": { "type": "string" }, "reportLanguage": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Structured analysis result", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReviewResult" } } } } }
      }
    },
    "/api/reviews/progressive/{verificationId}": {
      "get": {
        "summary": "Poll content-free source-verification state for a provisional report",
        "operationId": "getProgressiveVerification",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "verificationId", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "responses": { "200": { "description": "Verification status and content-free finding changes", "content": { "application/json": { "schema": { "type": "object" } } } }, "404": { "description": "Unknown, expired or unauthorized verification" } }
      }
    },
    "/api/reviews/progressive/{verificationId}/activate": {
      "post": {
        "summary": "Explicitly activate the completed verified report revision",
        "operationId": "activateProgressiveVerification",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "verificationId", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "responses": { "200": { "description": "Verified report revision", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReviewResult" } } } }, "409": { "description": "Verification is not ready or was already activated" } }
      }
    },
    "/api/progress/{operationId}": {
      "get": {
        "summary": "Poll progress of a running operation (204 when unknown/finished)",
        "operationId": "getProgress",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "operationId", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "responses": { "200": { "description": "Progress snapshot", "content": { "application/json": { "schema": { "type": "object" } } } }, "204": { "description": "No such operation for this client" } }
      },
      "delete": {
        "summary": "Request cancellation of a running operation (quota is refunded)",
        "operationId": "cancelOperation",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "operationId", "in": "path", "required": true, "schema": { "type": "string" } } ],
        "responses": { "200": { "description": "Cancellation requested", "content": { "application/json": { "schema": { "type": "object" } } } }, "404": { "description": "Unknown operation" }, "409": { "description": "Already finished" } }
      }
    },
    "/api/comparisons/prepare": {
      "post": {
        "summary": "Compare 2 or 3 contracts (counts as one operation, same daily quota)",
        "operationId": "prepareComparison",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "required": ["files"], "properties": { "files": { "type": "array", "minItems": 2, "maxItems": 3, "items": { "type": "string", "format": "binary" } } } } } } },
        "responses": { "200": { "description": "Comparison preparation/result", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/api/quick-clause-check": {
      "post": {
        "summary": "Focused check of a supplied clause text or one small file (bounded scope, same quota rules)",
        "operationId": "quickClauseCheck",
        "tags": ["analysis"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "text": { "type": "string", "maxLength": 20000 }, "file": { "type": "string", "format": "binary" } } } } } },
        "responses": { "200": { "description": "Quick-check result", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/api/contact/requests": {
      "post": {
        "summary": "Send a contact enquiry (rate-limited, never persisted)",
        "operationId": "sendContactRequest",
        "tags": ["contact"],
        "security": [ { "csrf": [] } ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email", "message", "consent", "privacyVersion", "language"], "properties": {
          "email": { "type": "string", "format": "email" },
          "organization": { "type": "string", "maxLength": 120 },
          "topic": { "type": "string", "enum": ["TEAM_PILOT", "INDIVIDUAL_FEEDBACK", "GENERAL"] },
          "message": { "type": "string", "minLength": 20, "maxLength": 3000 },
          "consent": { "type": "boolean" },
          "privacyVersion": { "type": "string", "enum": ["CONTACT_V1"] },
          "website": { "type": "string", "description": "Honeypot: must stay empty" },
          "language": { "type": "string", "enum": ["en", "de"] }
        } } } } },
        "responses": { "200": { "description": "Enquiry accepted or draft prepared", "content": { "application/json": { "schema": { "type": "object" } } } }, "429": { "description": "Rate limited" } }
      }
    },
    "/api/account/providers": {
      "get": {
        "summary": "List known BYOK LLM providers with their enabled flag and this account's configured state",
        "operationId": "listProviders",
        "tags": ["providers"],
        "description": "Optional BYOK LLM providers for registered accounts. Only providers enabled by the operator (`aletura.providers.enabled`, default: mistral only) are usable; send the selected provider as the `X-Aletura-Inference-Provider` header (OPENAI, AZURE_OPENAI, AZURE_CLAUDE) together with `X-Aletura-Inference-Mode: BYOK` on analysis calls. Non-Mistral providers never receive Aletura-funded traffic and carry no EU-residency claim.",
        "security": [ { "csrf": [] } ],
        "responses": { "200": { "description": "Provider capabilities and configured state (never secrets)", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/api/account/credentials": {
      "get": {
        "summary": "Configured non-Mistral provider credentials for this account (metadata only)",
        "operationId": "listProviderCredentials",
        "tags": ["providers"],
        "security": [ { "csrf": [] } ],
        "responses": { "200": { "description": "Configured provider credentials", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/api/account/credentials/{provider}": {
      "get": {
        "summary": "Metadata for one provider credential (lastFour, modelId, endpointHint; never the secret)",
        "operationId": "getProviderCredential",
        "tags": ["providers"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "enum": ["OPENAI", "AZURE_OPENAI", "AZURE_CLAUDE"] } } ],
        "responses": { "200": { "description": "Provider credential metadata", "content": { "application/json": { "schema": { "type": "object" } } } } }
      },
      "put": {
        "summary": "Save or replace a provider API key (step-up password required; stored AES-256-GCM encrypted, live-probed where the provider offers it)",
        "operationId": "saveProviderCredential",
        "tags": ["providers"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "enum": ["OPENAI", "AZURE_OPENAI", "AZURE_CLAUDE"] } } ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProviderCredentialRequest" } } } },
        "responses": { "200": { "description": "Updated credential metadata", "content": { "application/json": { "schema": { "type": "object" } } } } }
      },
      "delete": {
        "summary": "Delete a saved provider credential (step-up password required)",
        "operationId": "deleteProviderCredential",
        "tags": ["providers"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "enum": ["OPENAI", "AZURE_OPENAI", "AZURE_CLAUDE"] } } ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProviderPasswordRequest" } } } },
        "responses": { "200": { "description": "Credential metadata after deletion", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    },
    "/api/account/credentials/{provider}/model": {
      "put": {
        "summary": "Select the model used with this provider key (for Azure providers: the deployment name)",
        "operationId": "selectProviderModel",
        "tags": ["providers"],
        "security": [ { "csrf": [] } ],
        "parameters": [ { "name": "provider", "in": "path", "required": true, "schema": { "type": "string", "enum": ["OPENAI", "AZURE_OPENAI", "AZURE_CLAUDE"] } } ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProviderModelRequest" } } } },
        "responses": { "200": { "description": "Updated credential metadata", "content": { "application/json": { "schema": { "type": "object" } } } } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "csrf": {
        "type": "apiKey",
        "in": "header",
        "name": "X-XSRF-TOKEN",
        "description": "Send the XSRF-TOKEN cookie value in this header together with the JSESSIONID session cookie (both set by GET /api/auth/session)."
      }
    },
    "schemas": {
      "Session": {
        "type": "object",
        "properties": {
          "authenticated": { "type": "boolean" },
          "plan": { "type": "string", "example": "GUEST" },
          "passwordResetAvailable": { "type": "boolean" },
          "registrationAvailable": { "type": "boolean" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" },
          "status": { "type": "integer" }
        }
      },
      "ReviewResult": {
        "type": "object",
        "description": "Structured, evidence-linked analysis result. Evidence quotes stay in the source language; detected identifiers are masked unless restoreIdentifiers was requested.",
        "additionalProperties": true
      },
      "ProviderCredentialRequest": {
        "type": "object",
        "required": ["apiKey", "password"],
        "properties": {
          "apiKey": { "type": "string", "description": "Provider API key (never logged; stored AES-256-GCM encrypted and only sent to the provider's allowlisted endpoint)" },
          "password": { "type": "string", "description": "Account password for step-up re-authentication" },
          "endpointHint": { "type": "string", "description": "Required for Azure providers: the resource name, e.g. my-resource (validated and embedded into the allowlisted host)" },
          "modelId": { "type": "string", "description": "Optional model; for Azure providers the deployment name" }
        }
      },
      "ProviderModelRequest": {
        "type": "object",
        "required": ["modelId"],
        "properties": { "modelId": { "type": "string" } }
      },
      "ProviderPasswordRequest": {
        "type": "object",
        "required": ["password"],
        "properties": { "password": { "type": "string", "description": "Account password for step-up re-authentication" } }
      }
    }
  }
}
