openapi: 3.0.3
info:
  title: Aegis Core API
  version: 1.0.0
  description: |
    Public moderation API for text and handle safety scoring.
servers:
  - url: https://api.aegiscoreapi.com
    description: Production
  - url: http://localhost:8080
    description: Local development
security:
  - BearerAuth: []
  - ApiKeyHeader: []
tags:
  - name: Moderation
  - name: Feedback
  - name: Review Queue
  - name: Utility
paths:
  /healthz:
    get:
      tags: [Utility]
      summary: Health check
      security: []
      responses:
        "200":
          description: OK
          content:
            text/plain:
              schema:
                type: string
                example: ok

  /v1/categories:
    get:
      tags: [Moderation]
      summary: List available moderation categories
      responses:
        "200":
          description: Category list
          content:
            application/json:
              schema:
                type: object
                required: [categories]
                properties:
                  categories:
                    type: array
                    items:
                      $ref: "#/components/schemas/CategoryInfo"
        "401":
          $ref: "#/components/responses/UnauthorizedError"

  /v1/moderate:
    post:
      tags: [Moderation]
      summary: Moderate one text input
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ModerateRequest"
      responses:
        "200":
          description: Moderation result
          headers:
            X-Aegis-Request-ID:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModerateResponse"
        "400":
          $ref: "#/components/responses/BadRequestError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          $ref: "#/components/responses/SubscriptionInactiveError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "429":
          $ref: "#/components/responses/RateLimitedOrQuotaError"

  /v1/moderate/handle:
    post:
      tags: [Moderation]
      summary: Moderate a username/handle
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/HandleModerateRequest"
      responses:
        "200":
          description: Moderation result
          headers:
            X-Aegis-Request-ID:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModerateResponse"
        "400":
          $ref: "#/components/responses/BadRequestError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          $ref: "#/components/responses/SubscriptionInactiveError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "429":
          $ref: "#/components/responses/RateLimitedOrQuotaError"

  /v1/moderate/batch:
    post:
      tags: [Moderation]
      summary: Moderate up to 50 text items in one request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchModerateRequest"
      responses:
        "200":
          description: Moderation results
          headers:
            X-Aegis-Request-ID:
              schema:
                type: string
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/BatchModerateResult"
        "400":
          $ref: "#/components/responses/BadRequestError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          $ref: "#/components/responses/SubscriptionInactiveError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "429":
          $ref: "#/components/responses/RateLimitedOrQuotaError"

  /v1/moderate/structured:
    post:
      tags: [Moderation]
      summary: Moderate multiple profile/content fields in one request
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StructuredModerateRequest"
      responses:
        "200":
          description: Per-field moderation result
          headers:
            X-Aegis-Request-ID:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StructuredModerateResponse"
        "400":
          $ref: "#/components/responses/BadRequestError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          $ref: "#/components/responses/SubscriptionInactiveError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "429":
          $ref: "#/components/responses/RateLimitedOrQuotaError"

  /v1/feedback:
    post:
      tags: [Feedback]
      summary: Submit feedback for a previous moderation result
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FeedbackRequest"
      responses:
        "200":
          description: Accepted
          content:
            application/json:
              schema:
                type: object
                required: [status, request_id]
                properties:
                  status:
                    type: string
                    example: accepted
                  request_id:
                    type: string
        "400":
          $ref: "#/components/responses/BadRequestError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          $ref: "#/components/responses/SubscriptionInactiveError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/queue:
    get:
      tags: [Review Queue]
      summary: List pending review items
      parameters:
        - in: query
          name: limit
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 100
      responses:
        "200":
          description: Queue items
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/ReviewQueueItem"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          $ref: "#/components/responses/SubscriptionInactiveError"
        "403":
          $ref: "#/components/responses/ForbiddenError"

  /v1/queue/{request_id}/resolve:
    post:
      tags: [Review Queue]
      summary: Resolve one review queue item
      parameters:
        - in: path
          name: request_id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QueueResolveRequest"
      responses:
        "200":
          description: Resolved
          content:
            application/json:
              schema:
                type: object
                required: [status, request_id, resolution]
                properties:
                  status:
                    type: string
                    example: resolved
                  request_id:
                    type: string
                  resolution:
                    type: string
                    enum: [approved, rejected]
        "400":
          $ref: "#/components/responses/BadRequestError"
        "401":
          $ref: "#/components/responses/UnauthorizedError"
        "402":
          $ref: "#/components/responses/SubscriptionInactiveError"
        "403":
          $ref: "#/components/responses/ForbiddenError"
        "404":
          $ref: "#/components/responses/NotFoundError"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: Use `Authorization: Bearer <api_key>`.
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: Alternative header for API key auth.

  responses:
    BadRequestError:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    UnauthorizedError:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    ForbiddenError:
      description: Access forbidden
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    SubscriptionInactiveError:
      description: Subscription inactive
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    RateLimitedOrQuotaError:
      description: Rate limited or quota exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    NotFoundError:
      description: Not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
        status:
          type: string

    ScoreContext:
      type: object
      properties:
        is_reply:
          type: boolean
        mentions:
          type: array
          items:
            type: string

    Match:
      type: object
      required: [term, weight, multiplied]
      properties:
        term:
          type: string
        weight:
          type: integer
        multiplied:
          type: boolean
        window:
          type: string

    ModerateRequest:
      type: object
      required: [text]
      properties:
        text:
          type: string
        lang:
          type: string
          example: en
        categories:
          type: array
          items:
            type: string
            enum: [insult, threat, slur, abuse, self_harm, profanity, sexual, all]
        context:
          $ref: "#/components/schemas/ScoreContext"

    HandleModerateRequest:
      type: object
      properties:
        handle:
          type: string
        username:
          type: string
        categories:
          type: array
          items:
            type: string
        context:
          $ref: "#/components/schemas/ScoreContext"

    ModerateResponse:
      type: object
      required: [request_id, score, action]
      properties:
        request_id:
          type: string
        score:
          type: integer
        ml_score:
          type: number
          format: float
        action:
          type: string
          enum: [allow, warn, review, block]
        category_scores:
          type: object
          additionalProperties:
            type: integer
        flags:
          type: array
          items:
            type: string
        explanations:
          type: array
          items:
            type: string
        matches:
          type: array
          items:
            $ref: "#/components/schemas/Match"

    BatchModerateItem:
      type: object
      required: [id, text]
      properties:
        id:
          type: string
        text:
          type: string
        lang:
          type: string
        categories:
          type: array
          items:
            type: string
        context:
          $ref: "#/components/schemas/ScoreContext"

    BatchModerateRequest:
      type: object
      required: [items]
      properties:
        items:
          type: array
          maxItems: 50
          items:
            $ref: "#/components/schemas/BatchModerateItem"

    BatchModerateResult:
      allOf:
        - $ref: "#/components/schemas/ModerateResponse"
        - type: object
          required: [id]
          properties:
            id:
              type: string

    StructuredModerateRequest:
      type: object
      properties:
        username:
          type: string
        bio:
          type: string
        post_body:
          type: string
        categories:
          type: array
          items:
            type: string
        context:
          $ref: "#/components/schemas/ScoreContext"

    StructuredFieldResult:
      type: object
      required: [score, action]
      properties:
        score:
          type: integer
        ml_score:
          type: number
          format: float
        action:
          type: string
          enum: [allow, warn, review, block]
        category_scores:
          type: object
          additionalProperties:
            type: integer
        flags:
          type: array
          items:
            type: string
        explanations:
          type: array
          items:
            type: string
        matches:
          type: array
          items:
            $ref: "#/components/schemas/Match"

    StructuredModerateResponse:
      type: object
      required: [request_id, results, summary]
      properties:
        request_id:
          type: string
        results:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/StructuredFieldResult"
        summary:
          type: object
          required: [score, action]
          properties:
            score:
              type: integer
            ml_score:
              type: number
              format: float
            action:
              type: string
              enum: [allow, warn, review, block]
            flags:
              type: array
              items:
                type: string

    CategoryInfo:
      type: object
      required: [name, description]
      properties:
        name:
          type: string
        description:
          type: string

    FeedbackRequest:
      type: object
      required: [request_id, correct_action]
      properties:
        request_id:
          type: string
        correct_action:
          type: string
          enum: [allow, warn, review, block]
        note:
          type: string

    ReviewQueueItem:
      type: object
      required: [request_id, user_id, api_key, text, score, action, flags, created_at, expires_at]
      properties:
        request_id:
          type: string
        user_id:
          type: string
        api_key:
          type: string
        text:
          type: string
        score:
          type: integer
        action:
          type: string
        flags:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
        resolved_at:
          type: string
          format: date-time
          nullable: true
        resolution:
          type: string
        actor_note:
          type: string
        resolved_by:
          type: string

    QueueResolveRequest:
      type: object
      required: [resolution]
      properties:
        resolution:
          type: string
          enum: [approved, rejected]
        note:
          type: string
