openapi: 3.1.0 info: title: StellaOps Findings Ledger API version: 1.0.0 description: | OpenAPI specification for the Findings Ledger service. Unblocks LEDGER-OAS-61-001-DEV through LEDGER-OAS-63-001-DEV. contact: name: StellaOps API Team email: api@stella-ops.org license: name: AGPL-3.0-or-later identifier: AGPL-3.0-or-later servers: - url: https://api.stella-ops.org/v1 description: Production - url: https://api.staging.stella-ops.org/v1 description: Staging tags: - name: findings description: Finding management operations - name: projections description: Finding projections and views - name: evidence description: Evidence lookups and links - name: snapshots description: Time-travel and snapshot operations - name: attestation description: Attestation and verification - name: export description: Export and reporting paths: /findings: get: operationId: listFindings summary: List findings with pagination and filtering tags: [findings] parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/ProjectId' - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' - $ref: '#/components/parameters/SortBy' - $ref: '#/components/parameters/SortOrder' - name: status in: query schema: type: array items: $ref: '#/components/schemas/FindingStatus' - name: severity in: query schema: type: array items: $ref: '#/components/schemas/Severity' - name: component_purl in: query schema: type: string description: Filter by component PURL pattern - name: vulnerability_id in: query schema: type: string description: Filter by CVE or vulnerability ID - name: created_after in: query schema: type: string format: date-time - name: created_before in: query schema: type: string format: date-time responses: '200': description: Paginated list of findings content: application/json: schema: $ref: '#/components/schemas/FindingsListResponse' headers: ETag: schema: type: string description: Entity tag for caching '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' post: operationId: createFinding summary: Create a new finding tags: [findings] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateFindingRequest' responses: '201': description: Finding created content: application/json: schema: $ref: '#/components/schemas/Finding' headers: Location: schema: type: string format: uri '400': $ref: '#/components/responses/BadRequest' '409': $ref: '#/components/responses/Conflict' /findings/{findingId}: get: operationId: getFinding summary: Get finding by ID tags: [findings] parameters: - $ref: '#/components/parameters/FindingId' - name: include in: query schema: type: array items: type: string enum: [evidence, attestations, history, projections] description: Related data to include responses: '200': description: Finding details content: application/json: schema: $ref: '#/components/schemas/Finding' headers: ETag: schema: type: string '304': description: Not modified '404': $ref: '#/components/responses/NotFound' patch: operationId: updateFinding summary: Update finding status or metadata tags: [findings] parameters: - $ref: '#/components/parameters/FindingId' - name: If-Match in: header required: true schema: type: string description: ETag for optimistic concurrency requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateFindingRequest' responses: '200': description: Finding updated content: application/json: schema: $ref: '#/components/schemas/Finding' '412': $ref: '#/components/responses/PreconditionFailed' /findings/{findingId}/evidence: get: operationId: getFindingEvidence summary: Get evidence linked to a finding tags: [findings, evidence] parameters: - $ref: '#/components/parameters/FindingId' - name: artifact_type in: query schema: type: array items: type: string enum: [sbom, vex, scan_result, attestation, callgraph, runtime_facts] responses: '200': description: Evidence list content: application/json: schema: $ref: '#/components/schemas/EvidenceListResponse' /findings/{findingId}/attestations: get: operationId: getFindingAttestations summary: Get attestations for a finding tags: [findings, attestation] parameters: - $ref: '#/components/parameters/FindingId' responses: '200': description: Attestation list content: application/json: schema: $ref: '#/components/schemas/AttestationListResponse' /findings/{findingId}/attestation-pointers: get: operationId: getFindingAttestationPointers summary: Get attestation pointers linking finding to verification reports and attestation envelopes description: | Returns all attestation pointers for a finding. Attestation pointers link findings to verification reports, DSSE envelopes, SLSA provenance, VEX attestations, and other cryptographic evidence for explainability and audit trails. tags: [findings, attestation] parameters: - $ref: '#/components/parameters/FindingId' - $ref: '#/components/parameters/TenantId' responses: '200': description: List of attestation pointers content: application/json: schema: type: array items: $ref: '#/components/schemas/AttestationPointer' examples: verified_finding: summary: Finding with verified DSSE envelope value: - pointer_id: "a1b2c3d4-5678-90ab-cdef-123456789abc" finding_id: "f1234567-89ab-cdef-0123-456789abcdef" attestation_type: "DsseEnvelope" relationship: "VerifiedBy" attestation_ref: digest: "sha256:abc123def456789012345678901234567890123456789012345678901234abcd" storage_uri: "s3://attestations/envelope.json" payload_type: "application/vnd.in-toto+json" predicate_type: "https://slsa.dev/provenance/v1" signer_info: issuer: "https://fulcio.sigstore.dev" subject: "build@stella-ops.org" verification_result: verified: true verified_at: "2025-01-01T12:00:00Z" verifier: "cosign" verifier_version: "2.2.3" checks: - check_type: "SignatureValid" passed: true - check_type: "CertificateValid" passed: true created_at: "2025-01-01T10:00:00Z" created_by: "scanner-service" '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' /findings/{findingId}/attestation-summary: get: operationId: getFindingAttestationSummary summary: Get summary of attestations for a finding description: Returns aggregate counts and verification status for all attestations linked to a finding. tags: [findings, attestation] parameters: - $ref: '#/components/parameters/FindingId' - $ref: '#/components/parameters/TenantId' responses: '200': description: Attestation summary content: application/json: schema: $ref: '#/components/schemas/AttestationSummary' examples: partially_verified: summary: Finding with mixed verification status value: finding_id: "f1234567-89ab-cdef-0123-456789abcdef" attestation_count: 3 verified_count: 2 latest_attestation: "2025-01-01T12:00:00Z" attestation_types: ["DsseEnvelope", "SlsaProvenance", "VexAttestation"] overall_verification_status: "PartiallyVerified" '400': $ref: '#/components/responses/BadRequest' /attestation-pointers: post: operationId: createAttestationPointer summary: Create an attestation pointer linking a finding to an attestation artifact description: | Creates a pointer linking a finding to a verification report, DSSE envelope, or other attestation artifact. This enables explainability and cryptographic audit trails. The operation is idempotent - creating the same pointer twice returns the existing record. tags: [attestation] parameters: - $ref: '#/components/parameters/TenantId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateAttestationPointerRequest' examples: dsse_envelope: summary: Link finding to DSSE envelope value: finding_id: "f1234567-89ab-cdef-0123-456789abcdef" attestation_type: "DsseEnvelope" relationship: "VerifiedBy" attestation_ref: digest: "sha256:abc123def456789012345678901234567890123456789012345678901234abcd" storage_uri: "s3://attestations/envelope.json" payload_type: "application/vnd.in-toto+json" predicate_type: "https://slsa.dev/provenance/v1" verification_result: verified: true verified_at: "2025-01-01T12:00:00Z" verifier: "cosign" responses: '201': description: Attestation pointer created content: application/json: schema: $ref: '#/components/schemas/CreateAttestationPointerResponse' headers: Location: schema: type: string format: uri '200': description: Attestation pointer already exists (idempotent) content: application/json: schema: $ref: '#/components/schemas/CreateAttestationPointerResponse' '400': $ref: '#/components/responses/BadRequest' /attestation-pointers/{pointerId}: get: operationId: getAttestationPointer summary: Get attestation pointer by ID tags: [attestation] parameters: - name: pointerId in: path required: true schema: type: string format: uuid - $ref: '#/components/parameters/TenantId' responses: '200': description: Attestation pointer details content: application/json: schema: $ref: '#/components/schemas/AttestationPointer' '404': $ref: '#/components/responses/NotFound' /attestation-pointers/{pointerId}/verification: put: operationId: updateAttestationPointerVerification summary: Update verification result for an attestation pointer description: Updates or adds verification result to an existing attestation pointer. tags: [attestation] parameters: - name: pointerId in: path required: true schema: type: string format: uuid - $ref: '#/components/parameters/TenantId' requestBody: required: true content: application/json: schema: type: object required: - verification_result properties: verification_result: $ref: '#/components/schemas/VerificationResult' responses: '204': description: Verification result updated '404': $ref: '#/components/responses/NotFound' /attestation-pointers/search: post: operationId: searchAttestationPointers summary: Search attestation pointers with filters description: | Search for attestation pointers across findings using various filters. Useful for auditing, compliance reporting, and finding findings with specific attestation characteristics. tags: [attestation] parameters: - $ref: '#/components/parameters/TenantId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AttestationPointerSearchRequest' examples: find_verified: summary: Find all verified attestation pointers value: verification_status: "Verified" limit: 100 find_by_type: summary: Find SLSA provenance attestations value: attestation_types: ["SlsaProvenance"] created_after: "2025-01-01T00:00:00Z" find_by_signer: summary: Find attestations by signer identity value: signer_identity: "build@stella-ops.org" verification_status: "Verified" responses: '200': description: Search results content: application/json: schema: $ref: '#/components/schemas/AttestationPointerSearchResponse' '400': $ref: '#/components/responses/BadRequest' /findings/{findingId}/history: get: operationId: getFindingHistory summary: Get finding status history tags: [findings] parameters: - $ref: '#/components/parameters/FindingId' responses: '200': description: History entries content: application/json: schema: $ref: '#/components/schemas/HistoryListResponse' /projections: get: operationId: listProjections summary: List available projections tags: [projections] parameters: - $ref: '#/components/parameters/TenantId' responses: '200': description: Projection list content: application/json: schema: $ref: '#/components/schemas/ProjectionListResponse' /projections/{projectionId}: get: operationId: getProjection summary: Get projection data tags: [projections] parameters: - name: projectionId in: path required: true schema: type: string - name: filter in: query schema: type: string description: JSON filter expression - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Projection data content: application/json: schema: $ref: '#/components/schemas/ProjectionDataResponse' /snapshots: get: operationId: listSnapshots summary: List available snapshots tags: [snapshots] parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/ProjectId' responses: '200': description: Snapshot list content: application/json: schema: $ref: '#/components/schemas/SnapshotListResponse' post: operationId: createSnapshot summary: Create a point-in-time snapshot tags: [snapshots] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSnapshotRequest' responses: '202': description: Snapshot creation accepted content: application/json: schema: $ref: '#/components/schemas/SnapshotJob' /snapshots/{snapshotId}: get: operationId: getSnapshot summary: Get snapshot details tags: [snapshots] parameters: - name: snapshotId in: path required: true schema: type: string format: uuid responses: '200': description: Snapshot details content: application/json: schema: $ref: '#/components/schemas/Snapshot' /snapshots/{snapshotId}/findings: get: operationId: getSnapshotFindings summary: Get findings from a snapshot (time-travel query) tags: [snapshots] parameters: - name: snapshotId in: path required: true schema: type: string format: uuid - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Findings at snapshot point content: application/json: schema: $ref: '#/components/schemas/FindingsListResponse' /evidence: get: operationId: listEvidence summary: List evidence artifacts tags: [evidence] parameters: - $ref: '#/components/parameters/TenantId' - name: artifact_type in: query schema: type: array items: type: string - name: digest in: query schema: type: string pattern: '^sha256:[a-f0-9]{64}$' responses: '200': description: Evidence list content: application/json: schema: $ref: '#/components/schemas/EvidenceListResponse' /evidence/{evidenceId}: get: operationId: getEvidence summary: Get evidence artifact tags: [evidence] parameters: - name: evidenceId in: path required: true schema: type: string format: uuid responses: '200': description: Evidence details content: application/json: schema: $ref: '#/components/schemas/EvidenceArtifact' /export: post: operationId: createExport summary: Create export job for findings tags: [export] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateExportRequest' responses: '202': description: Export job created content: application/json: schema: $ref: '#/components/schemas/ExportJob' /export/{exportId}: get: operationId: getExport summary: Get export job status and download tags: [export] parameters: - name: exportId in: path required: true schema: type: string format: uuid responses: '200': description: Export job details content: application/json: schema: $ref: '#/components/schemas/ExportJob' /.well-known/openapi: get: operationId: getOpenApiSpec summary: Get OpenAPI specification description: Returns the OpenAPI specification for this API responses: '200': description: OpenAPI specification content: application/json: schema: type: object application/yaml: schema: type: object components: parameters: TenantId: name: X-Tenant-ID in: header required: true schema: type: string format: uuid description: Tenant identifier ProjectId: name: X-Project-ID in: header schema: type: string format: uuid description: Project identifier FindingId: name: findingId in: path required: true schema: type: string format: uuid description: Finding identifier PageSize: name: page_size in: query schema: type: integer minimum: 1 maximum: 1000 default: 100 PageToken: name: page_token in: query schema: type: string description: Continuation token for pagination SortBy: name: sort_by in: query schema: type: string enum: [created_at, updated_at, severity, status] default: created_at SortOrder: name: sort_order in: query schema: type: string enum: [asc, desc] default: desc schemas: Finding: type: object required: - id - tenant_id - vulnerability_id - component - status - severity - created_at properties: id: type: string format: uuid tenant_id: type: string format: uuid project_id: type: string format: uuid vulnerability_id: type: string description: CVE ID or vulnerability identifier component: $ref: '#/components/schemas/Component' status: $ref: '#/components/schemas/FindingStatus' severity: $ref: '#/components/schemas/Severity' cvss_score: type: number minimum: 0 maximum: 10 epss_score: type: number minimum: 0 maximum: 1 kev_listed: type: boolean reachability: $ref: '#/components/schemas/ReachabilityInfo' vex_status: type: string enum: [not_affected, affected, fixed, under_investigation] fix_available: type: boolean fix_version: type: string source: type: string description: Source of the finding (scanner name) labels: type: object additionalProperties: type: string created_at: type: string format: date-time updated_at: type: string format: date-time first_seen_at: type: string format: date-time last_seen_at: type: string format: date-time evidence_refs: type: array items: $ref: '#/components/schemas/EvidenceRef' attestation_refs: type: array items: $ref: '#/components/schemas/AttestationRef' FindingStatus: type: string enum: - open - triaged - in_progress - resolved - ignored - false_positive Severity: type: string enum: - critical - high - medium - low - info Component: type: object required: - purl properties: purl: type: string description: Package URL name: type: string version: type: string ecosystem: type: string digest: type: string ReachabilityInfo: type: object properties: state: type: string enum: [reachable, unreachable, potentially_reachable, unknown] confidence: type: number minimum: 0 maximum: 1 entry_points: type: array items: type: string EvidenceRef: type: object required: - id - digest properties: id: type: string format: uuid artifact_type: type: string digest: type: string uri: type: string format: uri AttestationRef: type: object required: - id properties: id: type: string format: uuid type: type: string digest: type: string CreateFindingRequest: type: object required: - vulnerability_id - component - severity properties: vulnerability_id: type: string component: $ref: '#/components/schemas/Component' severity: $ref: '#/components/schemas/Severity' source: type: string labels: type: object additionalProperties: type: string evidence_refs: type: array items: $ref: '#/components/schemas/EvidenceRef' UpdateFindingRequest: type: object properties: status: $ref: '#/components/schemas/FindingStatus' severity: $ref: '#/components/schemas/Severity' labels: type: object additionalProperties: type: string notes: type: string FindingsListResponse: type: object required: - findings - total_count properties: findings: type: array items: $ref: '#/components/schemas/Finding' total_count: type: integer next_page_token: type: string EvidenceArtifact: type: object required: - id - artifact_type - digest properties: id: type: string format: uuid artifact_type: type: string digest: type: string content_type: type: string size_bytes: type: integer storage_uri: type: string format: uri created_at: type: string format: date-time provenance: type: object EvidenceListResponse: type: object required: - evidence properties: evidence: type: array items: $ref: '#/components/schemas/EvidenceArtifact' total_count: type: integer next_page_token: type: string AttestationListResponse: type: object required: - attestations properties: attestations: type: array items: type: object total_count: type: integer AttestationPointer: type: object required: - pointer_id - finding_id - attestation_type - relationship - attestation_ref - created_at - created_by properties: pointer_id: type: string format: uuid finding_id: type: string attestation_type: type: string enum: - VerificationReport - DsseEnvelope - SlsaProvenance - VexAttestation - SbomAttestation - ScanAttestation - PolicyAttestation - ApprovalAttestation relationship: type: string enum: - VerifiedBy - AttestedBy - SignedBy - ApprovedBy - DerivedFrom attestation_ref: $ref: '#/components/schemas/AttestationRefDetail' verification_result: $ref: '#/components/schemas/VerificationResult' created_at: type: string format: date-time created_by: type: string metadata: type: object additionalProperties: true ledger_event_id: type: string format: uuid AttestationRefDetail: type: object required: - digest properties: digest: type: string pattern: '^sha256:[a-f0-9]{64}$' attestation_id: type: string format: uuid storage_uri: type: string format: uri payload_type: type: string description: DSSE payload type (e.g., application/vnd.in-toto+json) predicate_type: type: string description: SLSA/in-toto predicate type URI subject_digests: type: array items: type: string description: Digests of subjects covered by this attestation signer_info: $ref: '#/components/schemas/SignerInfo' rekor_entry: $ref: '#/components/schemas/RekorEntryRef' SignerInfo: type: object properties: key_id: type: string issuer: type: string description: OIDC issuer for keyless signing subject: type: string description: OIDC subject/identity certificate_chain: type: array items: type: string signed_at: type: string format: date-time RekorEntryRef: type: object properties: log_index: type: integer format: int64 log_id: type: string uuid: type: string integrated_time: type: integer format: int64 description: Unix timestamp when entry was integrated into the log VerificationResult: type: object required: - verified - verified_at properties: verified: type: boolean verified_at: type: string format: date-time verifier: type: string description: Verification tool name (e.g., cosign, notation) verifier_version: type: string policy_ref: type: string description: Reference to verification policy used checks: type: array items: $ref: '#/components/schemas/VerificationCheck' warnings: type: array items: type: string errors: type: array items: type: string VerificationCheck: type: object required: - check_type - passed properties: check_type: type: string enum: - SignatureValid - CertificateValid - CertificateNotExpired - CertificateNotRevoked - RekorEntryValid - TimestampValid - PolicyMet - IdentityVerified - IssuerTrusted passed: type: boolean details: type: string evidence: type: object additionalProperties: true AttestationSummary: type: object required: - finding_id - attestation_count - verified_count - attestation_types - overall_verification_status properties: finding_id: type: string attestation_count: type: integer verified_count: type: integer latest_attestation: type: string format: date-time attestation_types: type: array items: type: string overall_verification_status: type: string enum: - AllVerified - PartiallyVerified - NoneVerified - NoAttestations CreateAttestationPointerRequest: type: object required: - finding_id - attestation_type - relationship - attestation_ref properties: finding_id: type: string attestation_type: type: string enum: - VerificationReport - DsseEnvelope - SlsaProvenance - VexAttestation - SbomAttestation - ScanAttestation - PolicyAttestation - ApprovalAttestation relationship: type: string enum: - VerifiedBy - AttestedBy - SignedBy - ApprovedBy - DerivedFrom attestation_ref: $ref: '#/components/schemas/AttestationRefDetail' verification_result: $ref: '#/components/schemas/VerificationResult' created_by: type: string metadata: type: object additionalProperties: true CreateAttestationPointerResponse: type: object required: - success properties: success: type: boolean pointer_id: type: string format: uuid ledger_event_id: type: string format: uuid error: type: string AttestationPointerSearchRequest: type: object properties: finding_ids: type: array items: type: string attestation_types: type: array items: type: string enum: - VerificationReport - DsseEnvelope - SlsaProvenance - VexAttestation - SbomAttestation - ScanAttestation - PolicyAttestation - ApprovalAttestation verification_status: type: string enum: - Any - Verified - Unverified - Failed created_after: type: string format: date-time created_before: type: string format: date-time signer_identity: type: string description: Filter by signer subject/identity predicate_type: type: string description: Filter by SLSA/in-toto predicate type limit: type: integer minimum: 1 maximum: 1000 default: 100 offset: type: integer minimum: 0 default: 0 AttestationPointerSearchResponse: type: object required: - pointers - total_count properties: pointers: type: array items: $ref: '#/components/schemas/AttestationPointer' total_count: type: integer HistoryListResponse: type: object required: - entries properties: entries: type: array items: type: object properties: timestamp: type: string format: date-time actor: type: string action: type: string changes: type: object ProjectionListResponse: type: object required: - projections properties: projections: type: array items: type: object properties: id: type: string name: type: string description: type: string ProjectionDataResponse: type: object required: - data properties: data: type: array items: type: object total_count: type: integer next_page_token: type: string Snapshot: type: object required: - id - created_at - status properties: id: type: string format: uuid name: type: string description: type: string created_at: type: string format: date-time point_in_time: type: string format: date-time status: type: string enum: [pending, ready, expired, failed] finding_count: type: integer digest: type: string SnapshotListResponse: type: object required: - snapshots properties: snapshots: type: array items: $ref: '#/components/schemas/Snapshot' total_count: type: integer CreateSnapshotRequest: type: object properties: name: type: string description: type: string point_in_time: type: string format: date-time description: Optional specific point in time (defaults to now) SnapshotJob: type: object required: - id - status properties: id: type: string format: uuid status: type: string enum: [queued, processing, completed, failed] snapshot_id: type: string format: uuid progress: type: integer minimum: 0 maximum: 100 CreateExportRequest: type: object properties: format: type: string enum: [json, csv, sarif, cyclonedx, spdx] default: json filters: type: object properties: status: type: array items: $ref: '#/components/schemas/FindingStatus' severity: type: array items: $ref: '#/components/schemas/Severity' created_after: type: string format: date-time created_before: type: string format: date-time ExportJob: type: object required: - id - status properties: id: type: string format: uuid status: type: string enum: [queued, processing, completed, failed] format: type: string download_url: type: string format: uri expires_at: type: string format: date-time finding_count: type: integer Error: type: object required: - code - message properties: code: type: string message: type: string details: type: object trace_id: type: string responses: BadRequest: description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error' Forbidden: description: Forbidden content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Not found content: application/json: schema: $ref: '#/components/schemas/Error' Conflict: description: Conflict content: application/json: schema: $ref: '#/components/schemas/Error' PreconditionFailed: description: Precondition failed (ETag mismatch) content: application/json: schema: $ref: '#/components/schemas/Error' securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT oauth2: type: oauth2 flows: clientCredentials: tokenUrl: https://auth.stella-ops.org/oauth/token scopes: findings:read: Read findings findings:write: Write findings evidence:read: Read evidence snapshots:read: Read snapshots snapshots:write: Create snapshots export:write: Create exports security: - bearerAuth: [] - oauth2: [findings:read]