# OpenAPI 3.1 specification for StellaOps VexLens WebService openapi: 3.1.0 info: title: StellaOps VexLens API version: 0.1.0-draft description: | VexLens Consensus Engine API for computing VEX (Vulnerability Exploitability eXchange) status consensus from multiple sources. Supports weighted voting, lattice-based consensus, and authoritative-first resolution modes. Uses the platform error envelope and tenant header `X-StellaOps-Tenant`. servers: - url: https://api.stellaops.example.com description: Production - url: https://api.dev.stellaops.example.com description: Development security: - oauth2: [vexlens.viewer] - oauth2: [vexlens.operator] - oauth2: [vexlens.admin] tags: - name: Consensus description: Compute and query VEX consensus - name: Projections description: Query stored consensus projections - name: Issuers description: Manage trusted VEX document issuers - name: Statistics description: Consensus statistics and analytics paths: /api/v1/vexlens/consensus: post: summary: Compute consensus for a vulnerability-product pair description: | Computes VEX status consensus from all available statements for a vulnerability-product pair. Applies trust weighting, conflict detection, and returns a rationale for the decision. tags: [Consensus] operationId: computeConsensus parameters: - $ref: '#/components/parameters/Tenant' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ComputeConsensusRequest' examples: basic: summary: Basic consensus request value: vulnerabilityId: CVE-2024-1234 productKey: pkg:npm/lodash@4.17.21 with-options: summary: With consensus options value: vulnerabilityId: CVE-2024-1234 productKey: pkg:npm/lodash@4.17.21 mode: WeightedVote minimumWeightThreshold: 0.2 storeResult: true emitEvent: true responses: '200': description: Consensus computed successfully content: application/json: schema: $ref: '#/components/schemas/ComputeConsensusResponse' examples: unanimous: summary: Unanimous consensus value: vulnerabilityId: CVE-2024-1234 productKey: pkg:npm/lodash@4.17.21 status: not_affected justification: vulnerable_code_not_present confidenceScore: 0.95 outcome: Unanimous rationale: summary: "Unanimous consensus from 3 authoritative sources" factors: - "All statements agree on not_affected status" - "Vendor statement with weight 0.98" statusWeights: not_affected: 2.85 contributions: - statementId: stmt-vendor-001 issuerId: npm-security status: not_affected justification: vulnerable_code_not_present weight: 0.98 contribution: 0.34 isWinner: true conflicts: null projectionId: proj-abc123 computedAt: "2025-12-06T12:00:00Z" '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' default: $ref: '#/components/responses/Error' /api/v1/vexlens/consensus/batch: post: summary: Compute consensus for multiple pairs in batch description: | Computes VEX status consensus for multiple vulnerability-product pairs in a single request. Useful for bulk processing during ingestion or policy evaluation. tags: [Consensus] operationId: computeConsensusBatch parameters: - $ref: '#/components/parameters/Tenant' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ComputeConsensusBatchRequest' examples: batch: summary: Batch of 3 targets value: targets: - vulnerabilityId: CVE-2024-1234 productKey: pkg:npm/lodash@4.17.21 - vulnerabilityId: CVE-2024-5678 productKey: pkg:npm/express@4.18.2 - vulnerabilityId: CVE-2024-9012 productKey: pkg:maven/org.apache.logging.log4j/log4j-core@2.17.0 mode: WeightedVote storeResults: true emitEvents: true responses: '200': description: Batch consensus computed content: application/json: schema: $ref: '#/components/schemas/ComputeConsensusBatchResponse' default: $ref: '#/components/responses/Error' /api/v1/vexlens/projections: get: summary: Query consensus projections description: | Lists stored consensus projections with filtering and pagination. Projections are immutable snapshots of consensus computation results. tags: [Projections] operationId: queryProjections parameters: - $ref: '#/components/parameters/Tenant' - name: vulnerabilityId in: query description: Filter by vulnerability ID (partial match) schema: type: string - name: productKey in: query description: Filter by product key (partial match) schema: type: string - name: status in: query description: Filter by consensus status schema: $ref: '#/components/schemas/VexStatus' - name: outcome in: query description: Filter by consensus outcome schema: $ref: '#/components/schemas/ConsensusOutcome' - name: minimumConfidence in: query description: Minimum confidence score schema: type: number minimum: 0 maximum: 1 - name: computedAfter in: query description: Filter projections computed after this time schema: type: string format: date-time - name: computedBefore in: query description: Filter projections computed before this time schema: type: string format: date-time - name: statusChanged in: query description: Filter to only projections where status changed schema: type: boolean - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' - name: sortBy in: query description: Field to sort by schema: type: string enum: [ComputedAt, StoredAt, VulnerabilityId, ProductKey, ConfidenceScore] default: ComputedAt - name: sortDescending in: query description: Sort in descending order schema: type: boolean default: true responses: '200': description: Paginated projection list content: application/json: schema: $ref: '#/components/schemas/QueryProjectionsResponse' default: $ref: '#/components/responses/Error' /api/v1/vexlens/projections/{projectionId}: get: summary: Get a projection by ID tags: [Projections] operationId: getProjection parameters: - $ref: '#/components/parameters/Tenant' - $ref: '#/components/parameters/ProjectionId' responses: '200': description: Projection details content: application/json: schema: $ref: '#/components/schemas/ProjectionDetailResponse' '404': $ref: '#/components/responses/NotFound' default: $ref: '#/components/responses/Error' /api/v1/vexlens/projections/latest: get: summary: Get the latest projection for a vulnerability-product pair tags: [Projections] operationId: getLatestProjection parameters: - $ref: '#/components/parameters/Tenant' - name: vulnerabilityId in: query required: true description: Vulnerability ID schema: type: string - name: productKey in: query required: true description: Product key (PURL or CPE) schema: type: string responses: '200': description: Latest projection content: application/json: schema: $ref: '#/components/schemas/ProjectionDetailResponse' '404': $ref: '#/components/responses/NotFound' default: $ref: '#/components/responses/Error' /api/v1/vexlens/projections/history: get: summary: Get projection history for a vulnerability-product pair description: Returns the history of consensus projections in chronological order. tags: [Projections] operationId: getProjectionHistory parameters: - $ref: '#/components/parameters/Tenant' - name: vulnerabilityId in: query required: true schema: type: string - name: productKey in: query required: true schema: type: string - name: limit in: query description: Maximum number of history entries schema: type: integer minimum: 1 maximum: 100 default: 10 responses: '200': description: Projection history content: application/json: schema: $ref: '#/components/schemas/ProjectionHistoryResponse' default: $ref: '#/components/responses/Error' /api/v1/vexlens/issuers: get: summary: List registered issuers tags: [Issuers] operationId: listIssuers parameters: - $ref: '#/components/parameters/Tenant' - name: category in: query description: Filter by issuer category schema: $ref: '#/components/schemas/IssuerCategory' - name: minimumTrustTier in: query description: Minimum trust tier schema: $ref: '#/components/schemas/TrustTier' - name: status in: query description: Filter by issuer status schema: $ref: '#/components/schemas/IssuerStatus' - name: search in: query description: Search term for name or ID schema: type: string - $ref: '#/components/parameters/Limit' - $ref: '#/components/parameters/Offset' responses: '200': description: Issuer list content: application/json: schema: $ref: '#/components/schemas/IssuerListResponse' default: $ref: '#/components/responses/Error' post: summary: Register a new issuer tags: [Issuers] operationId: registerIssuer parameters: - $ref: '#/components/parameters/Tenant' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterIssuerRequest' examples: vendor: summary: Register a vendor issuer value: issuerId: npm-security name: npm Security Team category: Vendor trustTier: Authoritative initialKeys: - fingerprint: ABCD1234EFGH5678 keyType: Pgp algorithm: EdDSA metadata: description: Official npm security advisories uri: https://www.npmjs.com/advisories email: security@npmjs.com responses: '201': description: Issuer registered content: application/json: schema: $ref: '#/components/schemas/IssuerDetailResponse' '409': description: Issuer already exists $ref: '#/components/responses/Error' default: $ref: '#/components/responses/Error' /api/v1/vexlens/issuers/{issuerId}: get: summary: Get issuer details tags: [Issuers] operationId: getIssuer parameters: - $ref: '#/components/parameters/IssuerId' responses: '200': description: Issuer details content: application/json: schema: $ref: '#/components/schemas/IssuerDetailResponse' '404': $ref: '#/components/responses/NotFound' default: $ref: '#/components/responses/Error' delete: summary: Revoke an issuer tags: [Issuers] operationId: revokeIssuer parameters: - $ref: '#/components/parameters/IssuerId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RevokeRequest' responses: '204': description: Issuer revoked '404': $ref: '#/components/responses/NotFound' default: $ref: '#/components/responses/Error' /api/v1/vexlens/issuers/{issuerId}/keys: post: summary: Add a key to an issuer tags: [Issuers] operationId: addIssuerKey parameters: - $ref: '#/components/parameters/IssuerId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterKeyRequest' responses: '200': description: Key added content: application/json: schema: $ref: '#/components/schemas/IssuerDetailResponse' '404': $ref: '#/components/responses/NotFound' default: $ref: '#/components/responses/Error' /api/v1/vexlens/issuers/{issuerId}/keys/{fingerprint}: delete: summary: Revoke an issuer key tags: [Issuers] operationId: revokeIssuerKey parameters: - $ref: '#/components/parameters/IssuerId' - name: fingerprint in: path required: true description: Key fingerprint schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RevokeRequest' responses: '204': description: Key revoked '404': $ref: '#/components/responses/NotFound' default: $ref: '#/components/responses/Error' /api/v1/vexlens/statistics: get: summary: Get consensus statistics tags: [Statistics] operationId: getStatistics parameters: - $ref: '#/components/parameters/Tenant' responses: '200': description: Consensus statistics content: application/json: schema: $ref: '#/components/schemas/ConsensusStatisticsResponse' default: $ref: '#/components/responses/Error' components: parameters: Tenant: name: X-StellaOps-Tenant in: header description: Tenant identifier schema: type: string ProjectionId: name: projectionId in: path required: true description: Projection ID schema: type: string IssuerId: name: issuerId in: path required: true description: Issuer ID schema: type: string Limit: name: limit in: query description: Maximum number of items to return schema: type: integer minimum: 1 maximum: 100 default: 50 Offset: name: offset in: query description: Number of items to skip schema: type: integer minimum: 0 default: 0 schemas: VexStatus: type: string enum: [not_affected, affected, fixed, under_investigation] description: VEX status per OpenVEX specification VexJustification: type: string enum: - component_not_present - vulnerable_code_not_present - vulnerable_code_not_in_execute_path - vulnerable_code_cannot_be_controlled_by_adversary - inline_mitigations_already_exist description: Justification for not_affected status ConsensusMode: type: string enum: [HighestWeight, WeightedVote, Lattice, AuthoritativeFirst] description: | - HighestWeight: Single highest-weighted statement wins - WeightedVote: Weighted voting among all statements - Lattice: Most conservative status wins (affected > under_investigation > not_affected > fixed) - AuthoritativeFirst: Authoritative sources override others ConsensusOutcome: type: string enum: [Unanimous, Majority, Plurality, ConflictResolved, NoData, Indeterminate] description: Outcome of consensus computation IssuerCategory: type: string enum: [Vendor, Distributor, Community, Internal, Aggregator] description: Category of VEX document issuer TrustTier: type: string enum: [Authoritative, Trusted, Untrusted, Unknown] description: Trust level for an issuer IssuerStatus: type: string enum: [Active, Suspended, Revoked] description: Status of an issuer ComputeConsensusRequest: type: object required: [vulnerabilityId, productKey] properties: vulnerabilityId: type: string description: CVE or other vulnerability identifier productKey: type: string description: Product identifier (PURL or CPE) mode: $ref: '#/components/schemas/ConsensusMode' minimumWeightThreshold: type: number minimum: 0 maximum: 1 description: Minimum trust weight threshold for statements storeResult: type: boolean description: Store the result as a projection emitEvent: type: boolean description: Emit an event for the consensus result ComputeConsensusResponse: type: object required: - vulnerabilityId - productKey - status - confidenceScore - outcome - rationale - contributions - computedAt properties: vulnerabilityId: type: string productKey: type: string status: $ref: '#/components/schemas/VexStatus' justification: $ref: '#/components/schemas/VexJustification' confidenceScore: type: number minimum: 0 maximum: 1 outcome: $ref: '#/components/schemas/ConsensusOutcome' rationale: $ref: '#/components/schemas/ConsensusRationale' contributions: type: array items: $ref: '#/components/schemas/Contribution' conflicts: type: array items: $ref: '#/components/schemas/Conflict' projectionId: type: string description: ID of stored projection (if storeResult was true) computedAt: type: string format: date-time ConsensusRationale: type: object required: [summary, factors, statusWeights] properties: summary: type: string description: Human-readable summary of the decision factors: type: array items: type: string description: List of factors that influenced the decision statusWeights: type: object additionalProperties: type: number description: Total weight per status Contribution: type: object required: [statementId, status, weight, contribution, isWinner] properties: statementId: type: string issuerId: type: string status: $ref: '#/components/schemas/VexStatus' justification: $ref: '#/components/schemas/VexJustification' weight: type: number contribution: type: number description: Proportional contribution to consensus isWinner: type: boolean description: Whether this statement won the consensus Conflict: type: object required: [statement1Id, statement2Id, status1, status2, severity, resolution] properties: statement1Id: type: string statement2Id: type: string status1: $ref: '#/components/schemas/VexStatus' status2: $ref: '#/components/schemas/VexStatus' severity: type: string enum: [Low, Medium, High, Critical] resolution: type: string description: How the conflict was resolved ComputeConsensusBatchRequest: type: object required: [targets] properties: targets: type: array items: type: object required: [vulnerabilityId, productKey] properties: vulnerabilityId: type: string productKey: type: string minItems: 1 maxItems: 100 mode: $ref: '#/components/schemas/ConsensusMode' storeResults: type: boolean emitEvents: type: boolean ComputeConsensusBatchResponse: type: object required: [results, totalCount, successCount, failureCount, completedAt] properties: results: type: array items: $ref: '#/components/schemas/ComputeConsensusResponse' totalCount: type: integer successCount: type: integer failureCount: type: integer completedAt: type: string format: date-time QueryProjectionsResponse: type: object required: [projections, totalCount, offset, limit] properties: projections: type: array items: $ref: '#/components/schemas/ProjectionSummary' totalCount: type: integer offset: type: integer limit: type: integer ProjectionSummary: type: object required: - projectionId - vulnerabilityId - productKey - status - confidenceScore - outcome - statementCount - conflictCount - computedAt - statusChanged properties: projectionId: type: string vulnerabilityId: type: string productKey: type: string status: $ref: '#/components/schemas/VexStatus' justification: $ref: '#/components/schemas/VexJustification' confidenceScore: type: number outcome: type: string statementCount: type: integer conflictCount: type: integer computedAt: type: string format: date-time statusChanged: type: boolean ProjectionDetailResponse: allOf: - $ref: '#/components/schemas/ProjectionSummary' - type: object properties: tenantId: type: string rationaleSummary: type: string storedAt: type: string format: date-time previousProjectionId: type: string ProjectionHistoryResponse: type: object required: [vulnerabilityId, productKey, history, totalCount] properties: vulnerabilityId: type: string productKey: type: string history: type: array items: $ref: '#/components/schemas/ProjectionSummary' totalCount: type: integer IssuerListResponse: type: object required: [issuers, totalCount] properties: issuers: type: array items: $ref: '#/components/schemas/IssuerSummary' totalCount: type: integer IssuerSummary: type: object required: [issuerId, name, category, trustTier, status, keyCount, registeredAt] properties: issuerId: type: string name: type: string category: $ref: '#/components/schemas/IssuerCategory' trustTier: $ref: '#/components/schemas/TrustTier' status: $ref: '#/components/schemas/IssuerStatus' keyCount: type: integer registeredAt: type: string format: date-time IssuerDetailResponse: allOf: - $ref: '#/components/schemas/IssuerSummary' - type: object properties: keyFingerprints: type: array items: $ref: '#/components/schemas/KeyFingerprint' metadata: $ref: '#/components/schemas/IssuerMetadata' lastUpdatedAt: type: string format: date-time revokedAt: type: string format: date-time revocationReason: type: string KeyFingerprint: type: object required: [fingerprint, keyType, status, registeredAt] properties: fingerprint: type: string keyType: type: string enum: [Pgp, X509, Jwk, Ssh, Sigstore] algorithm: type: string status: type: string enum: [Active, Expired, Revoked] registeredAt: type: string format: date-time expiresAt: type: string format: date-time IssuerMetadata: type: object properties: description: type: string uri: type: string format: uri email: type: string format: email tags: type: array items: type: string RegisterIssuerRequest: type: object required: [issuerId, name, category, trustTier] properties: issuerId: type: string name: type: string category: $ref: '#/components/schemas/IssuerCategory' trustTier: $ref: '#/components/schemas/TrustTier' initialKeys: type: array items: $ref: '#/components/schemas/RegisterKeyRequest' metadata: $ref: '#/components/schemas/IssuerMetadata' RegisterKeyRequest: type: object required: [fingerprint, keyType] properties: fingerprint: type: string keyType: type: string enum: [Pgp, X509, Jwk, Ssh, Sigstore] algorithm: type: string expiresAt: type: string format: date-time RevokeRequest: type: object required: [reason] properties: reason: type: string minLength: 1 maxLength: 500 ConsensusStatisticsResponse: type: object required: - totalProjections - byStatus - byOutcome - averageConfidence - projectionsWithConflicts - statusChangesLast24h - computedAt properties: totalProjections: type: integer byStatus: type: object additionalProperties: type: integer byOutcome: type: object additionalProperties: type: integer averageConfidence: type: number projectionsWithConflicts: type: integer statusChangesLast24h: type: integer computedAt: type: string format: date-time Error: type: object required: [code, message] properties: code: type: string message: type: string details: type: object traceId: type: string responses: Error: description: Error response content: application/json: schema: $ref: '#/components/schemas/Error' BadRequest: description: Invalid request content: application/json: schema: $ref: '#/components/schemas/Error' examples: validation: value: code: VALIDATION_ERROR message: Invalid request parameters details: field: vulnerabilityId error: Required field missing NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' examples: notFound: value: code: NOT_FOUND message: Requested resource not found securitySchemes: oauth2: type: oauth2 flows: authorizationCode: authorizationUrl: https://auth.stellaops.example.com/oauth/authorize tokenUrl: https://auth.stellaops.example.com/oauth/token scopes: vexlens.viewer: Read access to consensus projections vexlens.operator: Compute consensus and manage projections vexlens.admin: Full access including issuer management