openapi: 3.0.3
info:
  title: Risk Score API
  version: v2.1
  description: Automated, signal-based domain risk indicator. No guarantees. No advice. No interpretation.
servers:
  - url: https://riskapi.analyses-web.com
paths:
  /risk-score:
    post:
      summary: Calculate domain risk score
      description: >
        Returns a deterministic 0..100 risk score and a list of signal IDs.
        Optional one-line explanations can be returned with `?explain=1`.
      security:
        - ApiKeyAuth: []
      parameters:
        - in: query
          name: explain
          required: false
          schema:
            type: boolean
          description: When true/1, include `explanations[]` (signal, category, severity, note).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RiskRequest"
            examples:
              basic:
                value:
                  url: "https://example.com"
      responses:
        "200":
          description: Risk score response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RiskResponse"
              examples:
                basic:
                  value:
                    riskScore: 12
                    riskLevel: low
                    signals:
                      - domain_age_lt_12_months
                      - dns_caa_missing
                    version: v2.1
                    meta:
                      cached: false
                      processingMs: 83
                      requestId: "b7f2d7b6d2d44d6aa0c96a5b1e9c7d7b"
                      cacheHours: 24
                      scoringPolicy: "2026-01"
                withExplain:
                  value:
                    riskScore: 42
                    riskLevel: medium
                    signals:
                      - dns_spf_allows_all
                      - dns_dmarc_missing
                      - dns_caa_missing
                    version: v2.1
                    meta:
                      cached: true
                      processingMs: 7
                      requestId: "d0b0d9b1b8a44a8c8a5c2b9c8f6d1a2c"
                      cacheHours: 24
                      scoringPolicy: "2026-01"
                    explanations:
                      - signal: dns_spf_allows_all
                        category: risk
                        severity: high
                        note: "SPF permissif (+all ou all sans -/~/?all) : facilite l'usurpation d'email."
                      - signal: dns_caa_missing
                        category: informational
                        severity: info
                        note: "CAA missing: very common; not meaningful on its own."
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_url:
                  value: { error: missing_url, requestId: "..." }
                url_too_long:
                  value: { error: url_too_long, requestId: "..." }
                invalid_url:
                  value: { error: invalid_url, requestId: "..." }
        "401":
          description: Unauthorized (missing/invalid key)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_api_key:
                  value: { error: missing_api_key, requestId: "..." }
                invalid_api_key:
                  value: { error: invalid_api_key, requestId: "..." }
        "503":
          description: Auth store unavailable
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                auth_unavailable:
                  value: { error: auth_unavailable, requestId: "..." }
        "429":
          description: Too many requests (rate limited) or quota exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                rate_limited:
                  value: { error: rate_limited, requestId: "..." }
                quota_exceeded:
                  value: { error: quota_exceeded, requestId: "..." }
  /usage:
    get:
      summary: Get current monthly usage for your API key
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Usage summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  requestId:
                    type: string
                  planName:
                    type: string
                  monthlyQuota:
                    type: integer
                  usedThisMonth:
                    type: integer
                  remainingThisMonth:
                    type: integer
                  monthBucketUtc:
                    type: string
                required:
                  - requestId
                  - planName
                  - monthlyQuota
                  - usedThisMonth
                  - remainingThisMonth
                  - monthBucketUtc

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  schemas:
    RiskRequest:
      type: object
      required: [url]
      properties:
        url:
          type: string
          description: Absolute HTTP/HTTPS URL (max 2048 chars).
          example: "https://example.com"
    RiskMeta:
      type: object
      required: [cached, processingMs, requestId, scoringPolicy]
      properties:
        cached:
          type: boolean
          description: True when served from cache.
        processingMs:
          type: integer
          description: Server-side processing time in milliseconds.
        requestId:
          type: string
          description: Request identifier (also returned as X-Request-Id header).
        cacheHours:
          type: integer
          nullable: true
          description: Cache TTL in hours (when caching is enabled).
        scoringPolicy:
          type: string
          description: Internal scoring band/version. Bump when weights/thresholds change.
          example: "2026-01"
    RiskExplanation:
      type: object
      required: [signal, category, severity, note]
      properties:
        signal:
          type: string
        category:
          type: string
          enum: [risk, informational]
        severity:
          type: string
          enum: [info, low, medium, high]
        note:
          type: string
          description: One-line explanation (limits/false positives may apply).
    RiskResponse:
      type: object
      required: [riskScore, riskLevel, signals, version, meta]
      properties:
        riskScore:
          type: integer
          minimum: 0
          maximum: 100
        riskLevel:
          type: string
          enum: [low, medium, high]
        signals:
          type: array
          items:
            type: string
          description: Stable signal IDs. May include informational signals (weight 0).
        version:
          type: string
          example: v2.1
        meta:
          $ref: "#/components/schemas/RiskMeta"
        explanations:
          type: array
          nullable: true
          description: Present only when `explain=1`.
          items:
            $ref: "#/components/schemas/RiskExplanation"
    ErrorResponse:
      type: object
      required: [error, requestId]
      properties:
        error:
          type: string
        requestId:
          type: string
