openapi: "3.1.0"
info:
  title: Detection Lab API
  description: >
    Behavioral bot detection scoring engine. Submit browser telemetry events
    and receive a multi-signal human/bot score.
  version: "1.0.0"
  contact:
    url: https://detectionlab.app
servers:
  - url: https://detectionlab.app
    description: Production
  - url: http://localhost:5555
    description: Local development

paths:
  /api/score:
    post:
      operationId: scoreBundle
      summary: Score a telemetry bundle
      description: >
        Stateless scoring endpoint. Accepts a full telemetry bundle (mouse,
        keyboard, scroll, fingerprint events) and returns a multi-signal
        human/bot score. Supports both authenticated (API key) and
        unauthenticated (demo) usage.
      parameters:
        - $ref: "#/components/parameters/XApiKey"
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/XEphemeral"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScoreRequest"
      responses:
        "200":
          description: Score report
          headers:
            X-Request-ID:
              schema:
                type: string
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScoreResponse"
        "400":
          description: Bad request (no data or no events)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    options:
      operationId: scoreOptions
      summary: CORS preflight
      responses:
        "204":
          description: Preflight OK

  /api/verify-bot:
    post:
      operationId: verifyBot
      summary: Verify an entity is a bot via behavioral analysis
      description: >
        Inverse CAPTCHA endpoint. Accepts the same telemetry bundle as
        /api/score but scores with inverted thresholds — machine precision,
        uniform timing, and superhuman throughput score HIGH. Returns a
        bot verification verdict (BOT_VERIFIED, UNCERTAIN, HUMAN_DETECTED)
        and bot type classification.
      parameters:
        - $ref: "#/components/parameters/XApiKey"
        - $ref: "#/components/parameters/XRequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScoreRequest"
      responses:
        "200":
          description: Bot verification result
          headers:
            X-Request-ID:
              schema:
                type: string
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotVerifyResult"
        "400":
          description: Bad request (no data or no events)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Rate limit exceeded
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
    options:
      operationId: verifyBotOptions
      summary: CORS preflight
      responses:
        "204":
          description: Preflight OK

  /api/verify-bot/session:
    post:
      operationId: createBotVerifySession
      summary: Create a continuous bot verification session
      description: >
        Creates a new session for continuous bot verification. Events can be
        appended incrementally, and the session re-scores on each append.
        Verdict flips (e.g., BOT_VERIFIED → HUMAN_DETECTED) are tracked and
        can trigger webhook notifications.
      parameters:
        - $ref: "#/components/parameters/XApiKey"
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/XEphemeral"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BotVerifySessionCreate"
      responses:
        "200":
          description: Session created
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                    format: uuid
                  created_at:
                    type: number
                  status:
                    type: string
                    enum: [created]
        "401":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/verify-bot/session/{session_id}/events:
    post:
      operationId: appendBotVerifyEvents
      summary: Append events to a continuous verification session
      description: >
        Appends telemetry events to an existing session, re-scores the full
        session, and returns the current state including verdict and any
        verdict flips detected.
      parameters:
        - name: session_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/XApiKey"
        - $ref: "#/components/parameters/XRequestId"
        - $ref: "#/components/parameters/XEphemeral"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScoreRequest"
      responses:
        "200":
          description: Session state after event append
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotVerifySessionState"
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/verify-bot/session/{session_id}:
    get:
      operationId: getBotVerifySession
      summary: Get session state
      description: >
        Returns the current state of a continuous verification session
        including score history, verdict flips, and signal breakdown.
      parameters:
        - name: session_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/XApiKey"
        - $ref: "#/components/parameters/XRequestId"
      responses:
        "200":
          description: Session state
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotVerifySessionState"
        "404":
          description: Session not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/signup:
    post:
      operationId: signup
      summary: Create a free sandbox API key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
      responses:
        "200":
          description: API key created
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_key:
                    type: string
                  tier:
                    type: string
                  credits:
                    type: integer
                  rate_limit_per_minute:
                    type: integer
        "400":
          description: Invalid email
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Key already exists
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/score-traffic:
    post:
      operationId: scoreTraffic
      summary: Score a traffic sample and get a shareable report (lead magnet)
      description: >
        Scores a telemetry bundle with the standard 34-signal pipeline,
        provisions a free sandbox API key for new email addresses, and stores
        a shareable report served at /traffic-report/{report_id}. No API key
        required; rate-limited per IP. The api_key field is only present when
        a new sandbox key was provisioned (shown once).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, events]
              properties:
                email:
                  type: string
                  format: email
                events:
                  type: array
                  items:
                    type: object
      responses:
        "200":
          description: Score result with shareable report reference
          content:
            application/json:
              schema:
                type: object
                properties:
                  overall_score:
                    type: number
                  verdict:
                    type: string
                  report_id:
                    type: string
                    nullable: true
                  report_url:
                    type: string
                    nullable: true
                  api_key:
                    type: string
                    nullable: true
        "400":
          description: Invalid email or missing events
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/keys:
    get:
      operationId: listKeys
      summary: List API keys for an email (masked)
      parameters:
        - name: email
          in: query
          required: true
          schema:
            type: string
            format: email
      responses:
        "200":
          description: Key list
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKeyInfo"

  /api/usage:
    get:
      operationId: getUsage
      summary: Get usage logs for an API key
      parameters:
        - name: key_id
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Usage data
          content:
            application/json:
              schema:
                type: object
                properties:
                  usage:
                    type: array
                    items:
                      type: object

  /health:
    get:
      operationId: healthCheck
      summary: Liveness probe
      responses:
        "200":
          description: Service is alive
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [ok]
                  version:
                    type: string

  /ready:
    get:
      operationId: readinessCheck
      summary: Readiness probe
      responses:
        "200":
          description: Service is ready
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [ready]
                  supabase:
                    type: string
        "503":
          description: Service degraded
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [degraded]
                  supabase:
                    type: string

components:
  parameters:
    XApiKey:
      name: X-API-Key
      in: header
      description: API key for authenticated access
      required: false
      schema:
        type: string
    XRequestId:
      name: X-Request-ID
      in: header
      description: Client-provided request trace ID (auto-generated if absent)
      required: false
      schema:
        type: string
        format: uuid

    XEphemeral:
      name: X-Ephemeral
      in: header
      description: >
        When set to "true", telemetry is processed in-memory only.
        Raw events are not logged. If HUMAN_DETECTED, only the verdict
        is returned (no signal breakdown). Audit log records ephemeral=true
        but omits event details.
      required: false
      schema:
        type: string
        enum: ["true", "false"]

  schemas:
    BotVerifySessionCreate:
      type: object
      properties:
        webhook_url:
          type: string
          format: uri
          description: URL to POST on verdict flips

    BotVerifySessionState:
      type: object
      properties:
        session_id:
          type: string
          format: uuid
        created_at:
          type: number
        event_count:
          type: integer
        current_verdict:
          type: string
          enum: [BOT_VERIFIED, UNCERTAIN, HUMAN_DETECTED]
        overall_score:
          type: number
          minimum: 0
          maximum: 100
        signals:
          type: object
        bot_type:
          $ref: "#/components/schemas/BotTypeResult"
        score_history:
          type: array
          items:
            type: object
            properties:
              timestamp:
                type: number
              overall_score:
                type: number
              verdict:
                type: string
              event_count:
                type: integer
        verdict_flips:
          type: array
          items:
            $ref: "#/components/schemas/VerdictFlipEvent"

    VerdictFlipEvent:
      type: object
      properties:
        timestamp:
          type: number
        from_verdict:
          type: string
        to_verdict:
          type: string
        event_count:
          type: integer
        overall_score:
          type: number

    ScoreRequest:
      type: object
      required: [events]
      properties:
        events:
          type: array
          items:
            $ref: "#/components/schemas/TelemetryEvent"
          minItems: 1

    TelemetryEvent:
      type: object
      required: [type]
      properties:
        type:
          type: string
          enum:
            - mousemove
            - click
            - keydown
            - scroll
            - hover
            - fingerprint
            - scroll_jump
            - injection_detected
            - page_enter
            - page_leave
        timestamp_ms:
          type: number
        x:
          type: number
        "y":
          type: number
        key:
          type: string
        delay_ms:
          type: number
        delta_y:
          type: number
        isTrusted:
          type: boolean
        data:
          type: object

    ScoreResponse:
      type: object
      properties:
        overall_score:
          type: number
          minimum: 0
          maximum: 100
        verdict:
          type: string
          enum: [PASS, SUSPICIOUS, FAIL]
        signals:
          type: object
          additionalProperties:
            type: number
        intent:
          type: object
        data_exposure:
          type: object
        raw_stats:
          type: object
        _meta:
          type: object
          description: Rate limit and credit info (authenticated requests only)
          properties:
            credits_remaining:
              type: integer
            rate_limit:
              type: integer
            rate_limit_remaining:
              type: integer

    ApiKeyInfo:
      type: object
      properties:
        id:
          type: string
        tier:
          type: string
        credits_remaining:
          type: integer
        rate_limit_per_minute:
          type: integer
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        last_used_at:
          type: string
          format: date-time
          nullable: true

    BotVerifyResult:
      type: object
      properties:
        overall_score:
          type: number
          minimum: 0
          maximum: 100
        verdict:
          type: string
          enum: [BOT_VERIFIED, UNCERTAIN, HUMAN_DETECTED]
        signals:
          type: object
          additionalProperties:
            type: object
            properties:
              score:
                type: number
              weight:
                type: number
              status:
                type: string
        bot_type:
          $ref: "#/components/schemas/BotTypeResult"
        trust_token:
          type: string
          description: >
            Signed JWT containing behavioral_trust_score, verdict, bot_type,
            verification_gates, and expiry. Only present for authenticated
            requests when PyJWT is installed and X-Ephemeral is not set.
        raw_stats:
          type: object

    BotTypeResult:
      type: object
      properties:
        bot_type:
          $ref: "#/components/schemas/BotType"
        confidence:
          type: number
          minimum: 0
          maximum: 1
        evidence:
          type: array
          items:
            type: string
        all:
          type: array
          items:
            type: object

    BotType:
      type: string
      enum:
        - autonomous_agent
        - human_larping
        - remote_controlled
        - replay_bot
        - unknown

    Error:
      type: object
      properties:
        error:
          type: string
