openapi: "3.1.0"
info:
  title: "Tamis API"
  version: "1.0.0"
  description: "REST, SSE and WebSocket API for Tamis moderation platform. This file is auto-generated by script/generate_api_docs.ts."
servers:
  -
    url: "http://localhost:5001"
    description: "Local app"
  -
    url: "http://localhost:3000"
    description: "Docker dev app"
paths:
  "/api/v1/auth/me":
    get:
      tags:
        - "Auth"
      summary: "Get current user"
      operationId: "getMe"
      security:
        -
          bearerAuth: []
      parameters: []
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MeResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/auth/sign_in":
    post:
      tags:
        - "Auth"
      summary: "Sign in user"
      operationId: "signIn"
      security: []
      parameters: []
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/AuthResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/SignInRequest"
  "/api/v1/auth/sign_up":
    post:
      tags:
        - "Auth"
      summary: "Create user account"
      operationId: "signUp"
      security: []
      parameters: []
      responses:
        201:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/AuthResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/SignUpRequest"
  "/api/v1/docs":
    get:
      tags:
        - "Docs"
      summary: "Redirect to API docs UI"
      operationId: "apiDocsRedirect"
      security: []
      parameters: []
      responses:
        302:
          description: "Redirect"
          headers:
            Location:
              description: "Signed download URL"
              schema:
                type: "string"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
  "/api/v1/projects":
    get:
      tags:
        - "Projects"
      summary: "List projects"
      operationId: "listProjects"
      security:
        -
          bearerAuth: []
      parameters: []
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/Project"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    post:
      tags:
        - "Projects"
      summary: "Create project"
      operationId: "createProject"
      security:
        -
          bearerAuth: []
      parameters: []
      responses:
        201:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProjectUpsertRequest"
  "/api/v1/projects/{project_id}":
    delete:
      tags:
        - "Projects"
      summary: "Delete project"
      operationId: "deleteProject"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    get:
      tags:
        - "Projects"
      summary: "Get project"
      operationId: "getProject"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    patch:
      tags:
        - "Projects"
      summary: "Update project"
      operationId: "updateProject"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProjectUpsertRequest"
  "/api/v1/projects/{project_id}/audit_logs":
    get:
      tags:
        - "Audit Logs"
      summary: "List project audit logs"
      operationId: "listProjectAuditLogs"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/AuditLog"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/projects/{project_id}/classifiers":
    get:
      tags:
        - "Classifiers"
      summary: "List project classifiers"
      operationId: "listProjectClassifiers"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/Classifier"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    post:
      tags:
        - "Classifiers"
      summary: "Create classifier"
      operationId: "createProjectClassifier"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        201:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ClassifierResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      description: "Classifier identity (`name`, `model_key`) is server-assigned from `classifier_type`. Only one classifier per type is allowed per project. Optional `settings.language_requirements` can gate execution by detected message language."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ClassifierUpsertRequest"
  "/api/v1/projects/{project_id}/classifiers/{classifier_id}":
    delete:
      tags:
        - "Classifiers"
      summary: "Delete classifier"
      operationId: "deleteProjectClassifier"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "classifier_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    patch:
      tags:
        - "Classifiers"
      summary: "Update classifier"
      operationId: "updateProjectClassifier"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "classifier_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ClassifierResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      description: "Classifier identity (`name`, `model_key`) is server-assigned from `classifier_type`. Only one classifier per type is allowed per project. Optional `settings.language_requirements` can gate execution by detected message language."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ClassifierUpsertRequest"
  "/api/v1/projects/{project_id}/credentials":
    get:
      tags:
        - "Credentials"
      summary: "List project credentials"
      operationId: "listProjectCredentials"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/ProjectCredential"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    post:
      tags:
        - "Credentials"
      summary: "Create project credential"
      operationId: "createProjectCredential"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        201:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectCredentialResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProjectCredentialCreateRequest"
  "/api/v1/projects/{project_id}/credentials/{credential_id}":
    delete:
      tags:
        - "Credentials"
      summary: "Revoke project credential"
      operationId: "deleteProjectCredential"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "credential_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    patch:
      tags:
        - "Credentials"
      summary: "Update project credential"
      operationId: "updateProjectCredential"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "credential_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectCredentialResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProjectCredentialUpdateRequest"
  "/api/v1/projects/{project_id}/credentials/{credential_id}/rotate":
    post:
      tags:
        - "Credentials"
      summary: "Rotate project credential token"
      operationId: "rotateProjectCredential"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "credential_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        201:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectCredentialResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ProjectCredentialRotateRequest"
  "/api/v1/projects/{project_id}/events/accepted/stream":
    get:
      tags:
        - "Realtime"
      summary: "Open SSE stream for accepted messages only"
      operationId: "streamProjectAcceptedMessages"
      security:
        -
          projectStreamToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "SSE stream opened"
          content:
            "text/event-stream":
              schema:
                type: "string"
        401:
          "$ref": "#/components/responses/Unauthorized"
      description: "Streams accepted-only finalized moderation outcomes. Emits only message.finalized events where status is accepted."
  "/api/v1/projects/{project_id}/events/stream":
    get:
      tags:
        - "Realtime"
      summary: "Open SSE stream for project events"
      operationId: "streamProjectEvents"
      security:
        -
          projectStreamToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "SSE stream opened"
          content:
            "text/event-stream":
              schema:
                type: "string"
        401:
          "$ref": "#/components/responses/Unauthorized"
      description: "Streams realtime project events. Use this for full moderation lifecycle consumption (message finalized/review events, processing trace events, and export lifecycle events)."
  "/api/v1/projects/{project_id}/events/ws":
    get:
      tags:
        - "Realtime"
      summary: "Open WebSocket stream for project events"
      operationId: "streamProjectEventsWebSocket"
      security:
        -
          projectStreamToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        101:
          description: "WebSocket protocol switched"
        401:
          "$ref": "#/components/responses/Unauthorized"
      x-websocket: true
  "/api/v1/projects/{project_id}/exports":
    post:
      tags:
        - "Exports"
      summary: "Create project export"
      operationId: "createProjectExport"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        202:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectExportResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
        429:
          description: "Export request cooldown active"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/ExportCreateRequest"
  "/api/v1/projects/{project_id}/exports/{export_id}":
    get:
      tags:
        - "Exports"
      summary: "Get project export"
      operationId: "getProjectExport"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "export_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectExportResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/projects/{project_id}/exports/{export_id}/download":
    get:
      tags:
        - "Exports"
      summary: "Download project export"
      operationId: "downloadProjectExport"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "export_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        302:
          description: "Redirect"
          headers:
            Location:
              description: "Signed download URL"
              schema:
                type: "string"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
  "/api/v1/projects/{project_id}/messages":
    get:
      tags:
        - "Messages"
      summary: "List project messages"
      operationId: "listProjectMessages"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/Message"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    post:
      tags:
        - "Messages"
      summary: "Create moderation message"
      operationId: "createProjectMessage"
      security:
        -
          projectAccessToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        202:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MessageResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/CreateMessageRequest"
  "/api/v1/projects/{project_id}/messages/{message_id}":
    get:
      tags:
        - "Messages"
      summary: "Get project message"
      operationId: "getProjectMessage"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "message_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MessageResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/projects/{project_id}/reviews":
    get:
      tags:
        - "Reviews"
      summary: "List in-review messages"
      operationId: "listProjectReviews"
      security:
        -
          projectAccessToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/Message"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/projects/{project_id}/reviews/{message_id}":
    get:
      tags:
        - "Reviews"
      summary: "Get in-review message details"
      operationId: "getProjectReview"
      security:
        -
          projectAccessToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "message_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ReviewResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/projects/{project_id}/reviews/{message_id}/approve":
    post:
      tags:
        - "Reviews"
      summary: "Approve in-review message"
      operationId: "approveProjectReview"
      security:
        -
          projectAccessToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "message_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MessageResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/projects/{project_id}/reviews/{message_id}/reject":
    post:
      tags:
        - "Reviews"
      summary: "Reject in-review message"
      operationId: "rejectProjectReview"
      security:
        -
          projectAccessToken: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "message_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/MessageResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/projects/{project_id}/rules":
    get:
      tags:
        - "Rules"
      summary: "List project moderation rules"
      operationId: "listProjectRules"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/ModerationRule"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    post:
      tags:
        - "Rules"
      summary: "Create moderation rule"
      operationId: "createProjectRule"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        201:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RuleResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RuleUpsertRequest"
  "/api/v1/projects/{project_id}/rules/{rule_id}":
    delete:
      tags:
        - "Rules"
      summary: "Delete moderation rule"
      operationId: "deleteProjectRule"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "rule_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    get:
      tags:
        - "Rules"
      summary: "Get moderation rule"
      operationId: "getProjectRule"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "rule_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RuleResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    patch:
      tags:
        - "Rules"
      summary: "Update moderation rule"
      operationId: "updateProjectRule"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "rule_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/RuleResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/RuleUpsertRequest"
  "/api/v1/projects/{project_id}/transformations":
    get:
      tags:
        - "Transformations"
      summary: "List project transformations"
      operationId: "listProjectTransformations"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      "$ref": "#/components/schemas/Transformation"
                  meta:
                    "$ref": "#/components/schemas/PaginationMeta"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    post:
      tags:
        - "Transformations"
      summary: "Create transformation"
      operationId: "createProjectTransformation"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        201:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/TransformationResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/TransformationUpsertRequest"
  "/api/v1/projects/{project_id}/transformations/{transformation_id}":
    delete:
      tags:
        - "Transformations"
      summary: "Delete transformation"
      operationId: "deleteProjectTransformation"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "transformation_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/DeleteResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    get:
      tags:
        - "Transformations"
      summary: "Get transformation"
      operationId: "getProjectTransformation"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "transformation_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/TransformationResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
    patch:
      tags:
        - "Transformations"
      summary: "Update transformation"
      operationId: "updateProjectTransformation"
      security:
        -
          bearerAuth: []
      parameters:
        -
          name: "project_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
        -
          name: "transformation_id"
          in: "path"
          required: true
          schema:
            type: "string"
            format: "uuid"
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/TransformationResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/TransformationUpsertRequest"
  "/api/v1/testing/password_resets/latest":
    get:
      tags:
        - "Testing"
      summary: "Get latest password reset URL (test only)"
      operationId: "testingLatestPasswordReset"
      security: []
      parameters: []
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/TestingPasswordResetResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
  "/api/v1/testing/projects/latest":
    get:
      tags:
        - "Testing"
      summary: "Get latest project credentials for a user (test only)"
      operationId: "testingLatestProject"
      security: []
      parameters: []
      responses:
        200:
          description: "Success"
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/TestingLatestProjectResponse"
        401:
          "$ref": "#/components/responses/Unauthorized"
        403:
          "$ref": "#/components/responses/Forbidden"
        404:
          "$ref": "#/components/responses/NotFound"
        422:
          "$ref": "#/components/responses/ValidationError"
components:
  securitySchemes:
    bearerAuth:
      type: "http"
      scheme: "bearer"
      bearerFormat: "JWT"
    projectAccessToken:
      type: "apiKey"
      in: "header"
      name: "Authorization"
      description: "Use Bearer PROJECT_ACCESS_TOKEN"
    projectStreamToken:
      type: "apiKey"
      in: "header"
      name: "Authorization"
      description: "Use Bearer PROJECT_STREAM_TOKEN (or token query param for browser SSE/WebSocket)"
  responses:
    Unauthorized:
      description: "Unauthenticated"
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Problem"
    Forbidden:
      description: "Forbidden"
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Problem"
    NotFound:
      description: "Resource not found"
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Problem"
    ValidationError:
      description: "Validation error"
      content:
        "application/json":
          schema:
            "$ref": "#/components/schemas/Problem"
  schemas:
    Problem:
      type: "object"
      properties:
        type:
          type: "string"
        title:
          type: "string"
        status:
          type: "integer"
        detail:
          type: "string"
        path:
          type: "string"
        errors:
          type: "array"
          items:
            type: "object"
            properties:
              field:
                type: "string"
              message:
                type: "string"
    PaginationMeta:
      type: "object"
      properties:
        page:
          type: "integer"
        limit:
          type: "integer"
        total_count:
          type: "integer"
    User:
      type: "object"
      properties:
        email:
          type: "string"
          format: "email"
    AuthResponse:
      type: "object"
      properties:
        token:
          type: "string"
        user:
          "$ref": "#/components/schemas/User"
    MeResponse:
      type: "object"
      properties:
        user:
          "$ref": "#/components/schemas/User"
    Project:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        name:
          type: "string"
        slug:
          type: "string"
          nullable: true
        status:
          type: "string"
        allowed_languages:
          type: "array"
          items:
            type: "string"
        settings:
          type: "object"
          additionalProperties: true
        retention_days:
          type: "integer"
        classifiers:
          type: "array"
          items:
            "$ref": "#/components/schemas/Classifier"
        webhook_url:
          type: "string"
          nullable: true
        user_id:
          type: "string"
          format: "uuid"
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    ProjectCredential:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        project_id:
          type: "string"
          format: "uuid"
        name:
          type: "string"
        kind:
          type: "string"
          enum:
            - "access"
            - "stream"
        active:
          type: "boolean"
        token:
          type: "string"
        token_preview:
          type: "string"
        token_scopes:
          type: "array"
          items:
            type: "string"
        last_used_at:
          type: "string"
          format: "date-time"
          nullable: true
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    ProjectCredentialResponse:
      type: "object"
      properties:
        credential:
          "$ref": "#/components/schemas/ProjectCredential"
    Classifier:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        project_id:
          type: "string"
          format: "uuid"
        name:
          type: "string"
        classifier_type:
          type: "string"
          enum:
            - "toxicity"
            - "toxicity_openai"
            - "spam"
        model_key:
          type: "string"
        enabled:
          type: "boolean"
        threshold_pct:
          type: "integer"
        action:
          type: "string"
          enum:
            - "reject"
            - "inreview"
            - "tagonly"
        priority:
          type: "integer"
        settings:
          type: "object"
          additionalProperties: true
          properties:
            language_requirements:
              "$ref": "#/components/schemas/ClassifierLanguageRequirements"
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    ClassifierLanguageRequirements:
      type: "object"
      properties:
        mode:
          type: "string"
          enum:
            - "any"
            - "allowlist"
            - "denylist"
        languages:
          type: "array"
          items:
            type: "string"
    ClassifierResponse:
      type: "object"
      properties:
        classifier:
          "$ref": "#/components/schemas/Classifier"
    ProjectResponse:
      type: "object"
      properties:
        project:
          "$ref": "#/components/schemas/Project"
    Message:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        project_id:
          type: "string"
          format: "uuid"
        content:
          type: "string"
        original_content:
          type: "string"
        status:
          type: "string"
          enum:
            - "pending"
            - "inreview"
            - "accepted"
            - "rejected"
        trigger:
          type: "string"
          nullable: true
        scores:
          type: "object"
          additionalProperties: true
        metadata:
          type: "object"
          additionalProperties: true
        external_id:
          type: "string"
          nullable: true
        fingerprint:
          type: "string"
          nullable: true
        blocked_at:
          type: "string"
          format: "date-time"
          nullable: true
        sent_at:
          type: "string"
          format: "date-time"
          nullable: true
        detected_language:
          type: "string"
          nullable: true
        review_reason:
          type: "string"
          nullable: true
        review_rule_label:
          type: "string"
          nullable: true
        review_requested_at:
          type: "string"
          format: "date-time"
          nullable: true
        reviewed_at:
          type: "string"
          format: "date-time"
          nullable: true
        reviewed_by_id:
          type: "string"
          format: "uuid"
          nullable: true
        review_decision_note:
          type: "string"
          nullable: true
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    MessageResponse:
      type: "object"
      properties:
        message:
          "$ref": "#/components/schemas/Message"
    ReviewEvent:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        event_type:
          type: "string"
          enum:
            - "requested"
            - "approved"
            - "rejected"
        from_status:
          type: "string"
          enum:
            - "pending"
            - "inreview"
            - "accepted"
            - "rejected"
          nullable: true
        to_status:
          type: "string"
          enum:
            - "pending"
            - "inreview"
            - "accepted"
            - "rejected"
          nullable: true
        reason:
          type: "string"
          nullable: true
        note:
          type: "string"
          nullable: true
        actor_user_id:
          type: "string"
          format: "uuid"
          nullable: true
        snapshot:
          type: "object"
          additionalProperties: true
        created_at:
          type: "string"
          format: "date-time"
    ReviewResponse:
      type: "object"
      properties:
        message:
          "$ref": "#/components/schemas/Message"
        events:
          type: "array"
          items:
            "$ref": "#/components/schemas/ReviewEvent"
    ModerationRule:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        project_id:
          type: "string"
          format: "uuid"
        rule_type:
          type: "string"
        pattern:
          type: "string"
        action:
          type: "string"
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    RuleResponse:
      type: "object"
      properties:
        rule:
          "$ref": "#/components/schemas/ModerationRule"
    Transformation:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        project_id:
          type: "string"
          format: "uuid"
        name:
          type: "string"
        description:
          type: "string"
          nullable: true
        transformation_type:
          type: "string"
          enum:
            - "regex"
            - "wordlist"
        action:
          type: "string"
          enum:
            - "mask"
            - "redact"
            - "replace"
        pattern:
          type: "string"
        replacement:
          type: "string"
          nullable: true
        apply_scope:
          type: "string"
          enum:
            - "nonrejected"
            - "allmessages"
        enabled:
          type: "boolean"
        priority:
          type: "integer"
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    TransformationResponse:
      type: "object"
      properties:
        transformation:
          "$ref": "#/components/schemas/Transformation"
    AuditLog:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        action:
          type: "string"
        payload:
          type: "object"
          additionalProperties: true
        project_id:
          type: "string"
          format: "uuid"
        user_id:
          type: "string"
          format: "uuid"
        created_at:
          type: "string"
          format: "date-time"
    ProjectExport:
      type: "object"
      properties:
        id:
          type: "string"
          format: "uuid"
        project_id:
          type: "string"
          format: "uuid"
        format:
          type: "string"
          enum:
            - "project_zip"
            - "messages_zip"
        artifact_mode:
          type: "string"
          nullable: true
        status:
          type: "string"
          enum:
            - "pending"
            - "processing"
            - "completed"
            - "failed"
            - "expired"
        file_name:
          type: "string"
          nullable: true
        content_type:
          type: "string"
          nullable: true
        byte_size:
          type: "integer"
          nullable: true
        sha256:
          type: "string"
          nullable: true
        resource_counts:
          type: "object"
          additionalProperties: true
          nullable: true
        started_at:
          type: "string"
          format: "date-time"
          nullable: true
        completed_at:
          type: "string"
          format: "date-time"
          nullable: true
        failed_at:
          type: "string"
          format: "date-time"
          nullable: true
        expires_at:
          type: "string"
          format: "date-time"
          nullable: true
        error_code:
          type: "string"
          nullable: true
        created_at:
          type: "string"
          format: "date-time"
        updated_at:
          type: "string"
          format: "date-time"
    ProjectExportResponse:
      type: "object"
      properties:
        export:
          "$ref": "#/components/schemas/ProjectExport"
    ExportCreateRequest:
      type: "object"
      required:
        - "mode"
      properties:
        mode:
          type: "string"
          enum:
            - "project_zip"
            - "messages_zip"
    DeleteResponse:
      type: "object"
      properties:
        deleted:
          type: "boolean"
    ProjectCredentialCreateRequest:
      type: "object"
      required:
        - "name"
        - "kind"
      properties:
        name:
          type: "string"
        kind:
          type: "string"
          enum:
            - "access"
            - "stream"
        token_scopes:
          type: "array"
          items:
            type: "string"
    ProjectCredentialUpdateRequest:
      type: "object"
      properties:
        name:
          type: "string"
        token_scopes:
          type: "array"
          items:
            type: "string"
    ProjectCredentialRotateRequest:
      type: "object"
      properties:
        token_scopes:
          type: "array"
          items:
            type: "string"
    ProjectUpsertRequest:
      type: "object"
      properties:
        name:
          type: "string"
        status:
          type: "string"
        allowed_languages:
          type: "array"
          items:
            type: "string"
        retention_days:
          type: "integer"
        settings:
          type: "object"
          additionalProperties: true
        webhook_url:
          type: "string"
        webhook_secret:
          type: "string"
    ClassifierUpsertRequest:
      type: "object"
      properties:
        classifier_type:
          type: "string"
          enum:
            - "toxicity"
            - "toxicity_openai"
            - "spam"
        enabled:
          type: "boolean"
        threshold_pct:
          type: "integer"
        action:
          type: "string"
          enum:
            - "reject"
            - "inreview"
            - "tagonly"
        priority:
          type: "integer"
        settings:
          type: "object"
          additionalProperties: true
          properties:
            language_requirements:
              "$ref": "#/components/schemas/ClassifierLanguageRequirements"
    CreateMessageRequest:
      type: "object"
      required:
        - "content"
      properties:
        content:
          type: "string"
        external_id:
          type: "string"
        metadata:
          type: "object"
          additionalProperties: true
    RuleUpsertRequest:
      type: "object"
      properties:
        rule_type:
          type: "string"
          enum:
            - "regex"
            - "wordlist"
            - "allowlist"
            - "language"
        action:
          type: "string"
          enum:
            - "reject"
            - "flagforreview"
        pattern:
          type: "string"
    TransformationUpsertRequest:
      type: "object"
      properties:
        name:
          type: "string"
        description:
          type: "string"
        transformation_type:
          type: "string"
          enum:
            - "regex"
            - "wordlist"
        action:
          type: "string"
          enum:
            - "mask"
            - "redact"
            - "replace"
        pattern:
          type: "string"
        replacement:
          type: "string"
        apply_scope:
          type: "string"
          enum:
            - "non_rejected"
            - "all_messages"
        enabled:
          type: "boolean"
        priority:
          type: "integer"
    SignUpRequest:
      type: "object"
      properties:
        user:
          type: "object"
          properties:
            email:
              type: "string"
              format: "email"
            password:
              type: "string"
            password_confirmation:
              type: "string"
    SignInRequest:
      type: "object"
      properties:
        user:
          type: "object"
          properties:
            email:
              type: "string"
              format: "email"
            password:
              type: "string"
    TestingLatestProjectResponse:
      type: "object"
      properties:
        project_id:
          type: "string"
          format: "uuid"
        access_token:
          type: "string"
        access_token_scopes:
          type: "array"
          items:
            type: "string"
        stream_token:
          type: "string"
    TestingPasswordResetResponse:
      type: "object"
      properties:
        reset_url:
          type: "string"
