openapi: 3.0.3
info:
  title: AIActify API
  version: 1.0.0
  description: EU AI Act Compliance Platform — Single Source of Truth
  contact:
    email: info@aiactify.eu

servers:
  - url: https://aiactify-backend.vercel.app/api
    description: Production
  - url: http://localhost:3001/api
    description: Local

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
        code: { type: string }

    User:
      type: object
      properties:
        id: { type: string, format: uuid }
        email: { type: string, format: email }
        company: { type: string }
        plan: { type: string, enum: [trial, starter, pro, agency, enterprise] }
        email_verified: { type: boolean }
        created_at: { type: string, format: date-time }

    Site:
      type: object
      properties:
        id: { type: string, format: uuid }
        user_id: { type: string, format: uuid }
        domain: { type: string }
        name: { type: string }
        site_key: { type: string }
        settings:
          type: object
          properties:
            labelStyle: { type: string, enum: [badge, minimal, verbose] }
            labelPosition: { type: string, enum: [bottom-right, bottom-left, top-right, top-left] }
            brandingColor: { type: string, example: '#3ECF8E' }
            logoUrl: { type: string, format: uri }
            schemaOrg: { type: boolean }
            scriptAuditor: { type: boolean }
            piiProtection: { type: boolean }
        created_at: { type: string, format: date-time }

    ContentItem:
      type: object
      properties:
        id: { type: string, format: uuid }
        site_id: { type: string, format: uuid }
        content_hash: { type: string }
        content_type: { type: string, enum: [text, image, video, audio] }
        url: { type: string }
        selector: { type: string }
        preview: { type: string }
        status: { type: string, enum: [pending, ai, hybrid, human] }
        ai_confidence: { type: number, minimum: 0, maximum: 1 }
        is_exempt: { type: boolean }
        exempt_reason: { type: string, enum: [artistic, satirical, fictional, editorial_control, obvious_ai] }
        validated_at: { type: string, format: date-time }
        detected_at: { type: string, format: date-time }

    AuditLog:
      type: object
      properties:
        id: { type: string, format: uuid }
        site_id: { type: string, format: uuid }
        user_id: { type: string, format: uuid }
        action: { type: string }
        details: { type: object }
        integrity_hash: { type: string }
        ip_address: { type: string }
        created_at: { type: string, format: date-time }

    PlanLimits:
      type: object
      properties:
        plan: { type: string }
        maxSites: { type: integer }
        contentLimit: { type: integer, nullable: true, description: 'null = unlimited' }

tags:
  - name: Auth
    description: Authentication & user management
  - name: Sites
    description: Website management
  - name: Content
    description: AI content items
  - name: Audit
    description: Immutable audit logs
  - name: Scanner
    description: Public AI risk scanner
  - name: Stripe
    description: Payments & subscriptions
  - name: Scripts
    description: Script auditor findings
  - name: Agents
    description: AI agent disclosures
  - name: Snippet
    description: Client-side enforcer script

paths:
  # ── Health ──────────────────────────────────────────────────────────────────
  /health:
    get:
      tags: [Auth]
      summary: Health check
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
                  database: { type: string, example: connected }

  # ── Auth ─────────────────────────────────────────────────────────────────────
  /auth/register:
    post:
      tags: [Auth]
      summary: Register new user
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                company: { type: string }
                startTrial: { type: boolean, default: false }
      responses:
        '201':
          description: User created — verification email sent
        '409':
          description: Email already registered
        '400':
          $ref: '#/components/schemas/Error'

  /auth/login:
    post:
      tags: [Auth]
      summary: Login
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
      responses:
        '200':
          description: JWT token returned
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
                  user: { $ref: '#/components/schemas/User' }
        '401':
          description: Invalid credentials or email not verified

  /auth/me:
    get:
      tags: [Auth]
      summary: Get current user
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  user: { $ref: '#/components/schemas/User' }

  /auth/verify-email:
    post:
      tags: [Auth]
      summary: Verify email with token
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token: { type: string }

  /auth/resend-verification:
    post:
      tags: [Auth]
      summary: Resend verification email (authenticated)

  /auth/resend-verification-by-email:
    post:
      tags: [Auth]
      summary: Resend verification email (unauthenticated)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }

  /auth/forgot-password:
    post:
      tags: [Auth]
      summary: Send password reset email
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }

  /auth/reset-password:
    post:
      tags: [Auth]
      summary: Reset password with token
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token, newPassword]
              properties:
                token: { type: string }
                newPassword: { type: string, minLength: 8 }

  /auth/change-password:
    post:
      tags: [Auth]
      summary: Change password (requires current password)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [currentPassword, newPassword]
              properties:
                currentPassword: { type: string }
                newPassword: { type: string, minLength: 8 }

  /auth/notification-settings:
    get:
      tags: [Auth]
      summary: Get notification preferences
    put:
      tags: [Auth]
      summary: Update notification preferences
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email_notifications: { type: boolean }
                weekly_report: { type: boolean }
                alert_on_violation: { type: boolean }

  # ── Sites ────────────────────────────────────────────────────────────────────
  /sites:
    get:
      tags: [Sites]
      summary: List all sites for user
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  sites:
                    type: array
                    items: { $ref: '#/components/schemas/Site' }
                  plan: { type: string }
                  maxSites: { type: integer }
                  contentLimit: { type: integer, nullable: true }
    post:
      tags: [Sites]
      summary: Create new site
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain: { type: string }
                name: { type: string }
                settings: { type: object }
      responses:
        '201':
          description: Site created
        '403':
          description: Site limit reached (SITE_LIMIT_REACHED)

  /sites/{id}:
    get:
      tags: [Sites]
      summary: Get single site
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
    put:
      tags: [Sites]
      summary: Update site settings / name / domain
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                domain: { type: string }
                settings: { $ref: '#/components/schemas/Site/properties/settings' }
    delete:
      tags: [Sites]
      summary: Delete site and all associated data
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }

  /sites/config/{siteKey}:
    get:
      tags: [Sites]
      summary: Get site config (public — used by snippet)
      security: []
      parameters:
        - name: siteKey
          in: path
          required: true
          schema: { type: string }

  # ── Content ──────────────────────────────────────────────────────────────────
  /content/site/{siteId}:
    get:
      tags: [Content]
      summary: Get all content items for a site
      parameters:
        - name: siteId
          in: path
          required: true
          schema: { type: string, format: uuid }

  /content/register:
    post:
      tags: [Content]
      summary: Register detected content items (called by snippet)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [siteKey, items]
              properties:
                siteKey: { type: string }
                items:
                  type: array
                  maxItems: 50
                  items:
                    type: object
                    properties:
                      hash: { type: string }
                      type: { type: string, enum: [text, image, video, audio] }
                      url: { type: string }
                      selector: { type: string }
                      preview: { type: string }
                      aiConfidence: { type: number }
      responses:
        '200':
          description: Registration result per item (may include limit_reached)

  /content/bulk-validate:
    put:
      tags: [Content]
      summary: Bulk validate up to 100 content items
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ids, status]
              properties:
                ids:
                  type: array
                  items: { type: string, format: uuid }
                  maxItems: 100
                status: { type: string, enum: [pending, ai, hybrid, human] }

  /content/{id}/validate:
    put:
      tags: [Content]
      summary: Validate a single content item (Human-in-the-loop)
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [status]
              properties:
                status: { type: string, enum: [pending, ai, hybrid, human] }
                notes: { type: string }

  /content/{id}/exempt:
    put:
      tags: [Content]
      summary: Set Art. 50(5) exemption on content item
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [isExempt]
              properties:
                isExempt: { type: boolean }
                exemptReason: { type: string, enum: [artistic, satirical, fictional, editorial_control, obvious_ai] }
                exemptNote: { type: string }

  /content/status/{siteKey}:
    get:
      tags: [Content]
      summary: Get content statuses by hash (used by snippet)
      security: []
      parameters:
        - name: siteKey
          in: path
          required: true
          schema: { type: string }
        - name: hashes
          in: query
          schema: { type: string, description: 'Comma-separated content hashes' }

  # ── Audit ─────────────────────────────────────────────────────────────────────
  /audit/site/{siteId}:
    get:
      tags: [Audit]
      summary: Get paginated audit logs for a site
      parameters:
        - name: siteId
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: limit
          in: query
          schema: { type: integer, default: 50, maximum: 500 }
        - name: action
          in: query
          schema: { type: string }

  /audit/all:
    get:
      tags: [Audit]
      summary: Get audit logs across all user sites

  /audit/export/{siteId}:
    get:
      tags: [Audit]
      summary: Export audit logs as JSON or CSV
      parameters:
        - name: siteId
          in: path
          required: true
          schema: { type: string, format: uuid }
        - name: format
          in: query
          schema: { type: string, enum: [json, csv], default: json }
        - name: from
          in: query
          schema: { type: string, format: date }
        - name: to
          in: query
          schema: { type: string, format: date }
      responses:
        '200':
          description: JSON export or CSV file download

  # ── Scanner ───────────────────────────────────────────────────────────────────
  /scanner/scan:
    post:
      tags: [Scanner]
      summary: Scan a URL for AI content (public)
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
                  description: Must be a public URL. Private IPs / metadata endpoints blocked (SSRF protection).
      responses:
        '200':
          description: Scan results
        '400':
          description: Invalid or blocked URL

  /scanner/lead:
    post:
      tags: [Scanner]
      summary: Capture lead from scanner page
      security: []

  # ── Stripe ────────────────────────────────────────────────────────────────────
  /stripe/create-checkout:
    post:
      tags: [Stripe]
      summary: Create Stripe Checkout session
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [planId]
              properties:
                planId:
                  type: string
                  enum: [starter, starter_yearly, pro_monthly, pro_yearly, agency, agency_yearly]
                successUrl: { type: string, format: uri }
                cancelUrl: { type: string, format: uri }
      responses:
        '200':
          description: Stripe Checkout URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  url: { type: string, format: uri }

  /stripe/webhook:
    post:
      tags: [Stripe]
      summary: Stripe webhook (Stripe → Backend)
      security: []
      description: Stripe signature verified via STRIPE_WEBHOOK_SECRET

  /stripe/verify-session:
    post:
      tags: [Stripe]
      summary: Verify checkout session and activate plan
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sessionId]
              properties:
                sessionId: { type: string }

  /stripe/sync-my-subscription:
    post:
      tags: [Stripe]
      summary: Sync own subscription status from Stripe

  /stripe/portal:
    post:
      tags: [Stripe]
      summary: Create Stripe Customer Portal session
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  url: { type: string, format: uri }

  # ── Scripts ───────────────────────────────────────────────────────────────────
  /scripts/report:
    post:
      tags: [Scripts]
      summary: Report a script finding from snippet (public)
      security: []

  /scripts/findings/{siteId}:
    get:
      tags: [Scripts]
      summary: Get script findings for a site
      parameters:
        - name: siteId
          in: path
          required: true
          schema: { type: string, format: uuid }

  /scripts/findings/{id}:
    put:
      tags: [Scripts]
      summary: Update / dismiss a script finding
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }

  /scripts/blocklist/{siteKey}:
    get:
      tags: [Scripts]
      summary: Get script blocklist for a site (used by snippet)
      security: []
      parameters:
        - name: siteKey
          in: path
          required: true
          schema: { type: string }

  # ── Agents ────────────────────────────────────────────────────────────────────
  /agents/config/{siteKey}:
    get:
      tags: [Agents]
      summary: Get agent disclosure config (public)
      security: []

  /agents/site/{siteId}:
    get:
      tags: [Agents]
      summary: List agent disclosures for a site
    post:
      tags: [Agents]
      summary: Create agent disclosure

  /agents/{id}:
    put:
      tags: [Agents]
      summary: Update agent disclosure
    delete:
      tags: [Agents]
      summary: Delete agent disclosure

  # ── Snippet ───────────────────────────────────────────────────────────────────
  /snippet/enforcer.js:
    get:
      tags: [Snippet]
      summary: Serve the client-side compliance enforcer script
      security: []
      parameters:
        - name: siteKey
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: JavaScript file
          content:
            application/javascript:
              schema: { type: string }

  /snippet/pii/check:
    post:
      tags: [Snippet]
      summary: Check text for PII patterns
      security: []

  /snippet/pii/anonymize:
    post:
      tags: [Snippet]
      summary: Anonymize PII in text
      security: []
