openapi: 3.1.0
info:
  title: LRDefender API
  version: 1.0.0
  description: |
    Device fingerprinting and bot detection API for LRDefender.

    Authenticate every request with your tenant API key via the `X-API-Key` header.
    Keys are issued from the LRDefender dashboard and typically use the `sk_` prefix.

servers:
  - url: https://api.lrdefender.lightningresearch.ai
    description: Production API

security:
  - ApiKeyAuth: []

tags:
  - name: Identification
    description: Device fingerprinting and visitor identification
  - name: Collector
    description: Server-side collector session issuance
  - name: Challenge
    description: Behavioral challenge verification
  - name: Health
    description: Service health and readiness

paths:
  /api/device-id:
    post:
      operationId: identifyDevice
      tags: [Identification]
      summary: Identify a device from signal payload
      description: |
        Primary device identification endpoint. Accepts browser, mobile, or server-collected
        signal payloads and returns a stable `deviceId`, confidence score, smart signals,
        and risk analysis.

        Set `X-Fingerprint-Update: 1` or `updateOnly: true` to perform a lightweight
        update without full re-identification. Set `X-API-Version: 2` or `apiVersion=2`
        for the unified v2 response envelope.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: X-Fingerprint-Update
          in: header
          required: false
          schema:
            type: string
            enum: ['0', '1', 'true', 'false']
          description: When `1` or `true`, perform update-only persistence.
        - name: X-API-Version
          in: header
          required: false
          schema:
            type: string
            enum: ['2']
          description: Request the unified API v2 response envelope.
        - name: X-Sealed-Results
          in: header
          required: false
          schema:
            type: string
            enum: ['true', 'false']
          description: When `true`, return tamper-proof sealed (encrypted) results.
        - name: apiVersion
          in: query
          required: false
          schema:
            type: string
            enum: ['2']
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceIdentificationRequest'
            examples:
              browserSignals:
                summary: Minimal browser signal payload
                value:
                  platform: MacIntel
                  timezone: Europe/Paris
                  language: en-US
                  screenWidth: 1920
                  screenHeight: 1080
                  devicePixelRatio: 1
                  userAgent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
                  webglVendor: Apple Inc.
                  webglRenderer: Apple GPU
                  privacy:
                    mode: full
                    consentStatus: granted
                    version: '1.0'
      responses:
        '200':
          description: Identification succeeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceIdentificationResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Tenant license or origin restriction
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/device-id/{requestId}:
    get:
      operationId: getIdentificationResult
      tags: [Identification]
      summary: Verify or retrieve an identification result
      description: |
        Poll the result of a prior identification request by `requestId`. Use the
        `requestId` returned from an async or sealed identification flow, or from
        the API v2 response envelope.

        Returns `404` when the request ID is unknown or has expired.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: requestId
          in: path
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 128
          description: Identification request identifier (e.g. `req_m3k9x2_a1b2c3`)
          example: req_m3k9x2_a1b2c3
      responses:
        '200':
          description: Identification result found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentificationResultResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Request ID not found or expired
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/device-recall/{deviceId}:
    get:
      operationId: recallDevice
      tags: [Identification]
      summary: Recall a device by ID
      description: |
        Look up a previously identified device by its 24-character hexadecimal
        `deviceId`. Returns persistence metadata, match strategy, and visit history.

        Also available without a path parameter at `GET /api/device-recall` when
        using a valid persistence cookie or device token from a prior identification.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: deviceId
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/HexId24'
          description: 24-character hexadecimal device identifier
          example: a1b2c3d4e5f6789012345678
      responses:
        '200':
          description: Device found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceRecallResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Device not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceNotFoundResponse'
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/collector/session:
    post:
      operationId: createCollectorSession
      tags: [Collector]
      summary: Create a collector session token
      description: |
        Issue a short-lived bearer token for browser-side collector traffic via
        `POST /collect/device`. Call this from your **server backend** only — never
        expose your API key in client-side code.

        The `origin` must match an allowed origin configured for your tenant.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CollectorSessionRequest'
            example:
              origin: https://app.example.com
              ttlSeconds: 300
              maxUses: 10
      responses:
        '200':
          description: Collector session issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CollectorSessionResponse'
        '400':
          description: Invalid request (e.g. missing origin)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Origin not allowed or license invalid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/health:
    get:
      operationId: getHealth
      tags: [Health]
      summary: Health check
      description: |
        Returns service health status including database, Redis, and queue connectivity.
        This is the first-party proxy alias used by edge integrations; the underlying
        service endpoint is also available at `GET /healthz` without the `/api` prefix.

        Does not require authentication when called via infrastructure probes, but
        may require an API key when routed through tenant-authenticated proxy paths.
      security: []
      responses:
        '200':
          description: Service is healthy or degraded but operational
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
        '503':
          description: Service is down (core dependencies unavailable)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'

  /api/challenge/verify:
    post:
      operationId: verifyChallenge
      tags: [Challenge]
      summary: Verify a behavioral challenge
      description: |
        Verify a challenge issued during device identification when
        `status: challenge_required` is returned. On success, returns a short-lived
        `passToken` to include on subsequent `POST /api/device-id` requests.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChallengeVerifyRequest'
            example:
              challengeToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
              solution: 42
      responses:
        '200':
          description: Challenge verified; pass token issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChallengeVerifyResponse'
        '400':
          description: Missing challenge token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Challenge verification failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Failed to issue pass token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Tenant secret API key (e.g. `sk_live_...`)

  schemas:
    HexId24:
      type: string
      pattern: '^[a-f0-9]{24}$'
      description: 24-character lowercase hexadecimal identifier
      example: a1b2c3d4e5f6789012345678

    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message
        code:
          type: string
          description: Machine-readable error code
      required: [error]

    ValidationError:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
        - type: object
          properties:
            code:
              type: string
              example: validation_error
            details:
              type: array
              items:
                type: object
                properties:
                  path:
                    type: string
                  message:
                    type: string

    DeviceIdentificationRequest:
      type: object
      description: Signal payload from browser, mobile, or server SDK
      properties:
        updateOnly:
          type: boolean
          description: When true, perform update-only without full identification
        deviceId:
          $ref: '#/components/schemas/HexId24'
        platform:
          type: string
          maxLength: 100
          example: MacIntel
        timezone:
          type: string
          maxLength: 100
          example: Europe/Paris
        language:
          type: string
          maxLength: 32
          example: en-US
        userAgent:
          type: string
          maxLength: 512
        screenWidth:
          type: integer
        screenHeight:
          type: integer
        devicePixelRatio:
          type: number
        webglVendor:
          type: string
        webglRenderer:
          type: string
        behavioral:
          type: object
          additionalProperties: true
          description: Behavioral biometrics signals
        privacy:
          type: object
          additionalProperties: true
          properties:
            mode:
              type: string
              enum: [full, limited]
            consentStatus:
              oneOf:
                - type: boolean
                - type: string
            version:
              type: string
        metadata:
          type: object
          additionalProperties: true
        clientDetection:
          type: object
          additionalProperties: true
      additionalProperties: true

    DeviceIdentificationResponse:
      type: object
      properties:
        deviceId:
          $ref: '#/components/schemas/HexId24'
        visitorId:
          $ref: '#/components/schemas/HexId24'
        confidence:
          type: number
          format: float
          minimum: 0
          maximum: 1
        status:
          type: string
          enum: [success, challenge_required, ignored]
        matchType:
          type: string
          description: Detection strategy (e.g. new, exact, fuzzy)
        identification:
          type: object
          properties:
            visitorId:
              $ref: '#/components/schemas/HexId24'
            confidence:
              type: object
              properties:
                score:
                  type: number
                version:
                  type: string
            visitorFound:
              type: boolean
            firstSeenAt:
              type: string
              format: date-time
              nullable: true
            lastSeenAt:
              type: string
              format: date-time
              nullable: true
        smartSignals:
          type: object
          additionalProperties: true
          description: Bot, VPN, tampering, and other smart signal results
        suspectScore:
          type: object
          properties:
            score:
              type: number
            level:
              type: string
        challenge:
          type: object
          description: Present when behavioral challenge is required or verified
          additionalProperties: true
        actionRequired:
          type: string
          enum: [challenge]
        experiments:
          type: array
          items:
            type: object
            additionalProperties: true
        meta:
          type: object
          properties:
            version:
              type: string
            processingTime:
              type: integer
              description: Server processing time in milliseconds
      required: [deviceId, status]

    IdentificationResultResponse:
      type: object
      description: Stored or async identification result for a given requestId
      properties:
        requestId:
          type: string
        status:
          type: string
          enum: [success, pending, challenge_required, not_found]
        deviceId:
          $ref: '#/components/schemas/HexId24'
        confidence:
          type: number
          format: float
        products:
          type: object
          additionalProperties: true
          description: API v2 product envelope when available
        timestamp:
          type: string
          format: date-time
      required: [requestId, status]

    DeviceRecallResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok]
        deviceId:
          $ref: '#/components/schemas/HexId24'
        visitorId:
          $ref: '#/components/schemas/HexId24'
        strategy:
          type: string
          description: Persistence resolution strategy
        issuedAt:
          type: string
          format: date-time
          nullable: true
        confidence:
          type: number
          format: float
        matchType:
          type: string
        lastSeen:
          type: string
          format: date-time
          nullable: true
        firstSeen:
          type: string
          format: date-time
          nullable: true
      required: [status, deviceId]

    DeviceNotFoundResponse:
      type: object
      properties:
        status:
          type: string
          enum: [not_found]
        deviceId:
          type: 'null'
      required: [status]

    CollectorSessionRequest:
      type: object
      properties:
        origin:
          type: string
          format: uri
          maxLength: 2048
          description: Allowed browser origin for collector traffic
          example: https://app.example.com
        ttlSeconds:
          type: integer
          minimum: 30
          maximum: 86400
          default: 300
          description: Session lifetime in seconds
        maxUses:
          type: integer
          minimum: 1
          maximum: 1000
          default: 1
          description: Maximum number of collector ingest calls per token
      additionalProperties: true

    CollectorSessionResponse:
      type: object
      properties:
        tokenType:
          type: string
          example: Bearer
        sessionToken:
          type: string
          description: Bearer token for `POST /collect/device`
        expiresAt:
          type: string
          format: date-time
        collectPath:
          type: string
          example: /collect/device
        maxUses:
          type: integer
        tenantId:
          $ref: '#/components/schemas/HexId24'
      required: [tokenType, sessionToken, expiresAt, collectPath]

    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok, degraded, down]
        degraded:
          type: boolean
        region:
          type: string
        uptimeSeconds:
          type: integer
        database:
          type: string
          enum: [connected, disconnected, in_memory_fallback]
        databaseConnected:
          type: boolean
        databaseDurable:
          type: boolean
        fallbackMode:
          type: boolean
        redis:
          type: string
          enum: [connected, disconnected]
        queue:
          type: string
          enum: [ready, degraded]
        redisConnected:
          type: boolean
        queueConnected:
          type: boolean
        timestamp:
          type: string
          format: date-time
      required: [status, timestamp]

    ChallengeVerifyRequest:
      type: object
      properties:
        challengeToken:
          type: string
          maxLength: 4096
          description: Challenge JWT from the identification response
        token:
          type: string
          maxLength: 4096
          description: Alias for challengeToken
        solution:
          oneOf:
            - type: string
            - type: number
          description: Challenge solution (e.g. proof-of-work nonce)
        proof:
          oneOf:
            - type: string
            - type: number
          description: Alias for solution
        response:
          oneOf:
            - type: string
            - type: number
          description: Alias for solution
      additionalProperties: true

    ChallengeVerifyResponse:
      type: object
      properties:
        status:
          type: string
          enum: [verified]
        tenantId:
          $ref: '#/components/schemas/HexId24'
        scope:
          type: string
          example: api:device-id
        passToken:
          type: string
          description: Short-lived token for subsequent identification requests
        expiresAt:
          type: string
          format: date-time
      required: [status, passToken, expiresAt]
