openapi: "3.0.3"
info:
  title: DevSecure Intelligence API
  version: "1.0.0"
  description: >
    Vulnerability Intelligence API providing enriched CVE data, proprietary RPS
    risk scoring, KEV enrichment, EPSS trends, and verified patch intelligence.
    All data refreshed daily at 04:00 UTC from the DevSecure BigQuery medallion pipeline.
  contact:
    name: DevSecure
    url: https://intel.devsecure.io
    email: api@devsecure.io
  license:
    name: Proprietary
    url: https://intel.pre.devsecure.io/terms

servers:
  - url: https://api-intel.devsecure.io
    description: Production API (Cloud Run europe-west2, fronted by Cloudflare)

x-tagGroups:
  - name: Public (no key required)
    tags: [System, KEV]
  - name: Authenticated (API key required)
    tags: [CVE, RPS, CWE, Stats, Patches]

tags:
  - name: CVE
    description: Core vulnerability intelligence lookups
  - name: RPS
    description: Risk Priority Score (lightweight)
  - name: CWE
    description: Search by weakness classification
  - name: KEV
    description: CISA Known Exploited Vulnerabilities (free tier)
  - name: Patches
    description: Verified patch intelligence (enterprise tier)
  - name: Stats
    description: Corpus statistics and data quality
  - name: System
    description: Health and status monitoring

security:
  - BearerAuth: []

paths:
  # ══════════════════════════════════════════════════════════════════════
  # CVE Endpoints
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/cve/{cve_id}:
    get:
      tags: [CVE]
      summary: Full enriched CVE lookup
      description: Returns all intelligence signals for a single CVE.
      parameters:
        - name: cve_id
          in: path
          required: true
          schema: { type: string }
          examples:
            log4shell:
              summary: Log4Shell (CVSS 10.0, KEV)
              value: CVE-2021-44228
            zerologon:
              summary: Zerologon (low CVSS, KEV)
              value: CVE-2020-1472
            windows_tcp:
              summary: Windows TCP/IP RCE (CVSS 9.8)
              value: CVE-2024-38063
            xz_utils:
              summary: xz-utils backdoor
              value: CVE-2024-3094
      responses:
        "200":
          description: RPS score and components
          content:
            application/json:
              schema: { $ref: "#/components/schemas/RpsResponse" }
        "400":
          description: Invalid CVE format
        "401":
          description: Missing or invalid API key
        "404":
          description: CVE not found

  # ══════════════════════════════════════════════════════════════════════
  # CWE Endpoint
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/cwe/{cwe_id}/cves:
    get:
      tags: [CWE]
      summary: Search CVEs by CWE
      description: Returns CVEs matching a CWE ID with optional filters.
      parameters:
        - name: cwe_id
          in: path
          required: true
          schema: { type: string, example: "CWE-502" }
        - name: severity
          in: query
          required: false
          schema: { type: string, enum: [CRITICAL, HIGH, MEDIUM, LOW, NONE] }
        - name: has_patch
          in: query
          required: false
          schema: { type: boolean }
        - name: is_kev
          in: query
          required: false
          schema: { type: boolean }
        - name: epss_min
          in: query
          required: false
          schema: { type: number, minimum: 0, maximum: 1 }
      responses:
        "200":
          description: Array of matching CVEs (max 1,000)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CweSearchResponse" }
        "400":
          description: Invalid CWE format
        "401":
          description: Missing or invalid API key

  # ══════════════════════════════════════════════════════════════════════
  # KEV Endpoint (Free Tier — No Auth)
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/kev:
    get:
      tags: [KEV]
      summary: Free KEV+ catalog
      description: >
        Full CISA KEV catalog enriched with RPS scores. **No authentication required.**
        Rate limited by IP (20 req/min, 200/day). Cached at edge (Cache-Control: public, max-age=3600).
      security: []
      responses:
        "200":
          description: KEV catalog with RPS scores
          headers:
            Cache-Control:
              schema: { type: string, example: "public, max-age=3600" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/KevResponse" }
        "429":
          description: Free tier rate limit exceeded (20 req/min, 200/day per IP)

  # ══════════════════════════════════════════════════════════════════════
  # Patches Endpoint (Enterprise)
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/patches/{cve_id}:
    get:
      tags: [Patches]
      summary: Patch intelligence
      description: >
        Verified before/after code pairs for a CVE. **Enterprise tier only.**
        Requires patch_access flag on API key.
      parameters:
        - name: cve_id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Patch data (or empty if none found)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PatchResponse" }
        "400":
          description: Invalid CVE format
        "401":
          description: Missing or invalid API key
        "403":
          description: Tier does not include patch access (enterprise only)
        "404":
          description: CVE not found

  # ══════════════════════════════════════════════════════════════════════
  # Stats Endpoint
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/stats:
    get:
      tags: [Stats]
      summary: Corpus statistics
      description: Returns aggregate corpus metrics including total CVEs, enrichment coverage, and last refresh timestamp. Public — no auth required.
      security: []
      responses:
        "200":
          description: Corpus stats
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StatsResponse" }

  # ══════════════════════════════════════════════════════════════════════
  # Trends Endpoints (Public)
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/trends:
    get:
      tags: [System]
      summary: Aggregated threat landscape data
      description: Returns CVE volume by year, top CVEs this week, KEV additions, CWE distribution, EPSS movers, and patch coverage. Public — no auth required.
      security: []
      responses:
        "200":
          description: Aggregated trends data

  /api/v1/trends/scatter:
    get:
      tags: [System]
      summary: RPS vs CVSS scatter plot data
      description: Returns sampled CVEs for visualising how RPS re-prioritises CVEs relative to CVSS alone. Includes all KEV CVEs and stratified non-KEV sample. Public — no auth required.
      security: []
      responses:
        "200":
          description: Scatter plot data points

  # ══════════════════════════════════════════════════════════════════════
  # POST /api/v1/prioritize (API release: v2.7.0, RPS scoring: v2.6.8)
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/prioritize:
    post:
      tags: [CVE]
      summary: Ranked priority order
      description: >
        Submit a list of CVE IDs and receive them ranked by RPS score with
        signal-based guidance. Transforms scanner output into an actionable
        remediation plan. Free tier not available.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [cve_ids]
              properties:
                cve_ids:
                  type: array
                  items: { type: string }
                  maxItems: 1000
                  example:
                    - CVE-2021-44228
                    - CVE-2020-1472
      responses:
        "200":
          description: Ranked results with BRD-contract-compliant fields
          content:
            application/json:
              schema:
                type: object
                required: [total, found, ranked, not_found, status]
                properties:
                  total: { type: integer, description: "Unique CVE IDs requested (deduped)" }
                  found: { type: integer, description: "CVEs matched against DevSecure corpus" }
                  not_found:
                    type: array
                    items: { type: string }
                    description: "CVE IDs not in corpus"
                  ranked:
                    type: array
                    items:
                      type: object
                      required: [cve_id, rps_score, priority_band, confidence, data_quality, sla_guidance, recommended_next_step]
                      properties:
                        cve_id: { type: string, example: "CVE-2021-44228" }
                        rps_score: { type: number, example: 99.7 }
                        priority_band:
                          type: string
                          enum: [critical_priority, high_priority, medium_priority, low_priority, informational, unknown]
                          example: critical_priority
                        rank_in_request: { type: integer, example: 1 }
                        confidence:
                          type: string
                          enum: [high, medium, low, none]
                          example: high
                        evidence: { $ref: "#/components/schemas/EvidenceBlock" }
                        data_quality: { $ref: "#/components/schemas/DataQualityBlock" }
                        reason: { type: string, example: "CISA KEV confirmed exploitation; EPSS 99th percentile." }
                        sla_guidance:
                          type: string
                          example: "Apply your organisation's SLA for critical-priority vulnerabilities."
                        recommended_next_step:
                          type: string
                          enum: [review_against_internal_sla, verify_applicability, monitor, no_action_recommended]
                          example: review_against_internal_sla
                  status: { type: string, enum: [success] }
        "400":
          description: Invalid input
        "401":
          description: Missing or invalid API key
        "403":
          description: Tier does not include this endpoint
        "429":
          description: Rate limit exceeded

  # ══════════════════════════════════════════════════════════════════════
  # System Endpoints (Unauthenticated)
  # ══════════════════════════════════════════════════════════════════════
  /api/v1/health:
    get:
      tags: [System]
      summary: Service health check
      description: Returns service status. Unauthenticated — for monitoring probes.
      security: []
      responses:
        "200":
          description: Service healthy
          content:
            application/json:
              schema: { $ref: "#/components/schemas/HealthResponse" }

  /api/v1/status:
    get:
      tags: [System]
      summary: Data freshness check
      description: Returns API status, cache status, BigQuery connection, and data freshness info.
      security: []
      responses:
        "200":
          description: Data freshness status
          content:
            application/json:
              schema: { $ref: "#/components/schemas/StatusResponse" }


# ══════════════════════════════════════════════════════════════════════════
# Components
# ══════════════════════════════════════════════════════════════════════════
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        API key provisioned per customer with tier-based rate limits.
        Free tier keys are issued at https://intel.devsecure.io/signup.

  schemas:
    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum: [bad_request, unauthorized, forbidden, not_found, rate_limited, internal_error]
            message:
              type: string
              description: Human-readable error message
            status:
              type: integer
              example: 401
            request_id: { type: string }
            docs_url: { type: string }

    CveResponse:
      type: object
      properties:
        cache: { type: string, enum: [hit, miss] }
        data:
          type: object
          properties:
            cve_id: { type: string, example: "CVE-2021-44228" }
            rps_score: { type: number, example: 104.98 }
            rps: { $ref: "#/components/schemas/RpsBlock" }
            signals: { $ref: "#/components/schemas/SignalsBlock" }
            explanation: { $ref: "#/components/schemas/ExplanationBlock" }
            data_quality: { $ref: "#/components/schemas/DataQualityBlock" }
            severity: { type: string, example: "CRITICAL" }
            published_date: { type: string }
            last_modified: { type: string }
            data_sources: { type: array, items: { type: string } }
        status: { type: string, enum: [success] }

    BatchResponse:
      type: object
      properties:
        results: { type: array, items: { type: object } }
        cache_stats:
          type: object
          properties:
            total_requested: { type: integer }
            cache_hits: { type: integer }
            cache_misses: { type: integer }
            not_found: { type: integer }
        not_found_cves: { type: array, items: { type: string } }
        status: { type: string }

    RecentResponse:
      type: object
      properties:
        since: { type: string }
        count: { type: integer }
        results: { type: array, items: { type: object } }
        status: { type: string }

    RpsResponse:
      type: object
      properties:
        cache: { type: string }
        data:
          type: object
          properties:
            cve_id: { type: string, example: "CVE-2021-44228" }
            rps: { $ref: "#/components/schemas/RpsBlock" }
            signals: { $ref: "#/components/schemas/SignalsBlock" }
            explanation: { $ref: "#/components/schemas/ExplanationBlock" }
        status: { type: string }

    RpsBlock:
      type: object
      properties:
        score: { type: number, example: 104.98 }
        band:
          type: string
          enum: [critical_priority, high_priority, medium_priority, low_priority, informational, unknown]
          example: critical_priority
        version: { type: string, example: "2.6.8" }
        formula: { type: string, example: "additive_base_kev_amplifier" }
        scored_at: { type: string, example: "2026-05-20T04:00:00Z" }

    SignalsBlock:
      type: object
      properties:
        cvss:
          type: object
          properties:
            score: { type: number, example: 10.0 }
            version: { type: string, example: "3.1" }
            weight: { type: number, example: 0.4 }
            contribution: { type: number, example: 0.4 }
            source: { type: string, example: "NVD" }
        epss:
          type: object
          properties:
            score: { type: number, example: 0.944 }
            percentile: { type: number, example: 0.999 }
            weight: { type: number, example: 0.3 }
            contribution: { type: number, example: 0.283 }
            source: { type: string, example: "FIRST" }
        kev:
          type: object
          properties:
            in_catalog: { type: boolean, example: true }
            date_added: { type: string, example: "2021-12-10" }
            amplifier: { type: number, example: 1.5 }
            source: { type: string, example: "CISA KEV" }
        exploit:
          type: object
          properties:
            public_exploit_available: { type: boolean, example: true }
            sources: { type: array, items: { type: string }, example: ["ExploitDB"] }
        patch:
          type: object
          properties:
            verified_patch_available: { type: boolean, example: true }
            patch_count: { type: integer, example: 12 }
            source: { type: string, example: "MoreFixes" }

    ExplanationBlock:
      type: object
      properties:
        summary: { type: string, example: "Critical priority: CISA KEV confirmed exploitation, EPSS 99th percentile..." }
        top_drivers: { type: array, items: { type: string } }
        guidance: { type: string, example: "Highest external threat. Active exploitation confirmed." }

    EvidenceBlock:
      type: object
      description: Evidence supporting the score
      properties:
        cvss:
          type: object
          properties:
            score: { type: number, example: 10.0 }
            version: { type: string, example: "v3.1" }
            source: { type: string, example: "NVD" }
        epss:
          type: object
          properties:
            score: { type: number, example: 0.97554 }
            percentile: { type: number, example: 0.99987 }
            source: { type: string, example: "FIRST" }
        kev:
          type: object
          properties:
            listed: { type: boolean, example: true }
            date_added: { type: string, example: "2021-12-10" }
        exploit_evidence: { type: array, items: { type: string }, example: ["EDB-50592", "GHSA-jfh8-c2jp-5v3q"] }
        patch_available: { type: boolean, example: true }

    DataQualityBlock:
      type: object
      properties:
        completeness:
          type: string
          enum: [complete, partial, sparse]
          example: complete
        stale_signals: { type: array, items: { type: string } }
        notes: { type: array, items: { type: string } }
        missing_fields: { type: array, items: { type: string }, example: [] }
        last_source_refresh:
          type: object
          properties:
            nvd: { type: string, example: "2026-05-20T03:10:00Z" }
            epss: { type: string, example: "2026-05-20T02:00:00Z" }
            kev: { type: string, example: "2026-05-20T01:30:00Z" }
            morefixes: { type: string, example: "2026-05-19T22:00:00Z" }

    CweSearchResponse:
      type: object
      properties:
        cwe_id: { type: string }
        filters: { type: object }
        count: { type: integer }
        results: { type: array, items: { type: object } }
        status: { type: string }

    KevResponse:
      type: object
      properties:
        count: { type: integer }
        last_updated: { type: string }
        results: { type: array, items: { type: object } }
        status: { type: string }
        note: { type: string }

    PatchResponse:
      type: object
      properties:
        cve_id: { type: string }
        patch_available: { type: boolean }
        total_commits: { type: integer }
        total_files: { type: integer }
        commits: { type: array, items: { type: object } }
        status: { type: string }

    StatsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            total_cves: { type: integer }
            cves_with_cvss: { type: integer }
            pct_cvss: { type: number }
            cves_with_epss: { type: integer }
            pct_epss: { type: number }
            cves_with_kev: { type: integer }
            cves_with_cwe: { type: integer }
            cves_with_patch: { type: integer }
            cves_fully_enriched: { type: integer }
            last_refresh: { type: string }
        status: { type: string }

    HealthResponse:
      type: object
      properties:
        service: { type: string }
        status: { type: string }
        version: { type: string }
        cache_status: { type: string }
        timestamp: { type: string }

    StatusResponse:
      type: object
      properties:
        api_status: { type: string }
        cache_status: { type: string }
        bigquery_status: { type: string }
        data_freshness:
          type: object
          properties:
            last_dbt_run_utc: { type: string }
            hours_since_refresh: { type: number }
            is_stale: { type: boolean }
        version: { type: string }
        timestamp: { type: string }
