openapi: 3.0.3 info: title: StellaOps Findings Ledger API version: 1.0.0-beta1 description: >- Canonical, aggregation-only surface for append-only findings events, projections, and Merkle anchoring metadata. Aligns with schema in docs/modules/findings-ledger/schema.md. servers: - url: https://{env}.ledger.api.stellaops.local description: Default environment-scoped host variables: env: default: prod enum: [dev, staging, prod, airgap] - url: https://ledger.{region}.offline.bundle description: Offline bundle host for air-gapped deployments variables: region: default: local enum: [local] security: - bearerAuth: [] - mTLS: [] paths: /v1/ledger/events: get: summary: List ledger events operationId: listLedgerEvents tags: [ledger] parameters: - $ref: '#/components/parameters/TenantId' - name: chainId in: query required: false schema: type: string format: uuid - name: sinceSequence in: query schema: type: integer minimum: 0 - name: limit in: query schema: type: integer default: 200 maximum: 1000 responses: '200': description: Paged ledger events in deterministic order (chainId, sequence_No asc) headers: Req-Cursor: schema: type: string content: application/json: schema: $ref: '#/components/schemas/LedgerEventPage' post: summary: Append deterministic ledger event operationId: appendLedgerEvent tags: [ledger] parameters: - $ref: '#/components/parameters/TenantId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/LedgerEventAppendRequest' responses: '201': description: Event persisted content: application/json: schema: $ref: '#/components/schemas/LedgerEvent' '409': description: Hash/sequence conflict (non-deterministic input) /v1/ledger/projections/findings: get: summary: Get latest projection for findings operationId: listFindingProjections tags: [projections] parameters: - $ref: '#/components/parameters/TenantId' - name: findingId in: query schema: type: string - name: policyVersion in: query schema: type: string - name: status in: query schema: type: string - name: limit in: query schema: type: integer default: 200 maximum: 1000 responses: '200': description: Projection rows with cycleHash for replay validation content: application/json: schema: $ref: '#/components/schemas/FindingProjectionPage' /v1/ledger/export/findings: get: summary: Export findings in deterministic order operationId: exportFindings tags: [export] parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/Shape' - $ref: '#/components/parameters/SinceSequence' - $ref: '#/components/parameters/UntilSequence' - $ref: '#/components/parameters/SinceObservedAt' - $ref: '#/components/parameters/UntilObservedAt' - name: finding_status in: query schema: { type: string } - name: severity in: query schema: { type: number } - name: risk_profile_version in: query schema: { type: string } - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Paged findings export headers: X-Stella-Next-Page-Token: schema: { type: string } X-Stella-Result-Count: schema: { type: integer } content: application/json: schema: $ref: '#/components/schemas/FindingExportPage' application/x-ndjson: schema: type: string description: NDJSON stream of FindingExportItem /v1/ledger/export/vex: get: summary: Export VEX statements operationId: exportVex tags: [export] parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/Shape' - $ref: '#/components/parameters/SinceSequence' - $ref: '#/components/parameters/UntilSequence' - $ref: '#/components/parameters/SinceObservedAt' - $ref: '#/components/parameters/UntilObservedAt' - name: product_id in: query schema: { type: string } - name: advisory_id in: query schema: { type: string } - name: status in: query schema: { type: string } - name: statement_type in: query schema: { type: string } - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Paged VEX export headers: X-Stella-Next-Page-Token: schema: { type: string } X-Stella-Result-Count: schema: { type: integer } content: application/json: schema: $ref: '#/components/schemas/VexExportPage' application/x-ndjson: schema: type: string description: NDJSON stream of VexExportItem /v1/ledger/export/advisories: get: summary: Export advisories operationId: exportAdvisories tags: [export] parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/Shape' - $ref: '#/components/parameters/SinceSequence' - $ref: '#/components/parameters/UntilSequence' - $ref: '#/components/parameters/SinceObservedAt' - $ref: '#/components/parameters/UntilObservedAt' - name: severity in: query schema: { type: string } - name: source in: query schema: { type: string } - name: cwe_id in: query schema: { type: string } - name: kev in: query schema: { type: boolean } - name: cvss_version in: query schema: { type: string } - name: cvss_score_min in: query schema: { type: number } - name: cvss_score_max in: query schema: { type: number } - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Paged advisory export headers: X-Stella-Next-Page-Token: schema: { type: string } X-Stella-Result-Count: schema: { type: integer } content: application/json: schema: $ref: '#/components/schemas/AdvisoryExportPage' application/x-ndjson: schema: type: string description: NDJSON stream of AdvisoryExportItem /v1/ledger/export/sboms: get: summary: Export SBOMs operationId: exportSboms tags: [export] parameters: - $ref: '#/components/parameters/TenantId' - $ref: '#/components/parameters/Shape' - $ref: '#/components/parameters/SinceSequence' - $ref: '#/components/parameters/UntilSequence' - $ref: '#/components/parameters/SinceObservedAt' - $ref: '#/components/parameters/UntilObservedAt' - name: subject_digest in: query schema: { type: string } - name: sbom_format in: query schema: { type: string } - name: component_purl in: query schema: { type: string } - name: contains_native in: query schema: { type: boolean } - name: slsa_build_type in: query schema: { type: string } - $ref: '#/components/parameters/PageSize' - $ref: '#/components/parameters/PageToken' responses: '200': description: Paged SBOM export headers: X-Stella-Next-Page-Token: schema: { type: string } X-Stella-Result-Count: schema: { type: integer } content: application/json: schema: $ref: '#/components/schemas/SbomExportPage' application/x-ndjson: schema: type: string description: NDJSON stream of SbomExportItem /v1/ledger/attestations: get: summary: List attestation verifications operationId: listLedgerAttestations tags: [attestation] parameters: - $ref: '#/components/parameters/TenantId' - name: artifactId in: query schema: { type: string } - name: findingId in: query schema: { type: string } - name: attestationId in: query schema: { type: string } - name: status in: query schema: type: string enum: [verified, failed, unknown] - name: sinceRecordedAt in: query schema: { type: string, format: date-time } - name: untilRecordedAt in: query schema: { type: string, format: date-time } - $ref: '#/components/parameters/AttestationLimit' - $ref: '#/components/parameters/PageToken' responses: '200': description: Paged attestation verifications headers: X-Stella-Next-Page-Token: schema: { type: string } X-Stella-Result-Count: schema: { type: integer } content: application/json: schema: $ref: '#/components/schemas/AttestationExportPage' application/x-ndjson: schema: type: string description: NDJSON stream of AttestationExportItem /.well-known/openapi: get: summary: Serve Findings Ledger OpenAPI document operationId: getOpenApi tags: [metadata] responses: '200': description: OpenAPI YAML document content: application/yaml: schema: type: string components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT mTLS: type: mutualTLS parameters: TenantId: name: X-Stella-Tenant in: header required: true schema: type: string Shape: name: shape in: query required: true schema: type: string enum: [canonical, compact] SinceSequence: name: since_sequence in: query schema: type: integer minimum: 0 UntilSequence: name: until_sequence in: query schema: type: integer minimum: 0 SinceObservedAt: name: since_observed_at in: query schema: type: string format: date-time UntilObservedAt: name: until_observed_at in: query schema: type: string format: date-time PageSize: name: page_size in: query schema: type: integer default: 500 maximum: 5000 PageToken: name: page_token in: query schema: type: string AttestationLimit: name: limit in: query schema: type: integer default: 200 maximum: 1000 schemas: LedgerEvent: type: object required: [event] properties: event: type: object required: [id, type, tenant, chainId, sequence, policyVersion, occurredAt, recordedAt, payload] properties: id: type: string format: uuid type: type: string description: ledger_event_type (see schema.md ยง2.2) tenant: type: string chainId: type: string format: uuid sequence: type: integer policyVersion: type: string finding: type: object properties: id: { type: string } artifactId: { type: string } vulnId: { type: string } actor: type: object properties: id: { type: string } type: { type: string } occurredAt: type: string format: date-time recordedAt: type: string format: date-time payload: type: object additionalProperties: true evidenceBundleRef: type: string eventHash: type: string previousHash: type: string merkleLeafHash: type: string LedgerEventPage: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/LedgerEvent' nextCursor: type: string LedgerEventAppendRequest: type: object required: [id, type, tenant, chainId, sequence, policyVersion, occurredAt, payload] properties: id: type: string format: uuid type: type: string tenant: type: string chainId: type: string format: uuid sequence: type: integer policyVersion: type: string finding: type: object properties: id: { type: string } artifactId: { type: string } vulnId: { type: string } actor: type: object properties: id: { type: string } type: { type: string } occurredAt: type: string format: date-time payload: type: object additionalProperties: true evidenceBundleRef: type: string previousHash: type: string description: Optional; validated if supplied merkleLeafHash: type: string description: Optional; server recomputes to validate determinism FindingProjection: type: object required: [tenantId, findingId, policyVersion, status, cycleHash] properties: tenantId: { type: string } findingId: { type: string } policyVersion: { type: string } status: { type: string } severity: type: number format: double riskScore: type: number format: double riskSeverity: type: string riskProfileVersion: type: string riskExplanationId: type: string format: uuid labels: type: object additionalProperties: true currentEventId: type: string format: uuid cycleHash: type: string updatedAt: type: string format: date-time FindingProjectionPage: type: object required: [items] properties: items: type: array items: $ref: '#/components/schemas/FindingProjection' nextCursor: type: string ExportProvenance: type: object properties: policyVersion: { type: string } cycleHash: { type: string } ledgerEventHash: { type: string } FindingExportItem: type: object properties: eventSequence: { type: integer } observedAt: { type: string, format: date-time } findingId: { type: string } policyVersion: { type: string } status: { type: string } severity: { type: number, format: double } cycleHash: { type: string } evidenceBundleRef: { type: string } provenance: $ref: '#/components/schemas/ExportProvenance' labels: type: object additionalProperties: true VexExportItem: type: object properties: eventSequence: { type: integer } observedAt: { type: string, format: date-time } vexStatementId: { type: string } productId: { type: string } status: { type: string } statementType: { type: string } knownExploited: { type: boolean } cycleHash: { type: string } provenance: $ref: '#/components/schemas/ExportProvenance' AdvisoryExportItem: type: object properties: eventSequence: { type: integer } published: { type: string, format: date-time } advisoryId: { type: string } source: { type: string } title: { type: string } severity: { type: string } cvssScore: { type: number, format: double } cvssVector: { type: string } kev: { type: boolean } cycleHash: { type: string } provenance: $ref: '#/components/schemas/ExportProvenance' SbomExportItem: type: object properties: eventSequence: { type: integer } createdAt: { type: string, format: date-time } sbomId: { type: string } subjectDigest: { type: string } sbomFormat: { type: string } componentsCount: { type: integer } hasVulnerabilities: { type: boolean } cycleHash: { type: string } provenance: $ref: '#/components/schemas/ExportProvenance' FindingExportPage: type: object properties: items: type: array items: { $ref: '#/components/schemas/FindingExportItem' } nextPageToken: { type: string } VexExportPage: type: object properties: items: type: array items: { $ref: '#/components/schemas/VexExportItem' } nextPageToken: { type: string } AdvisoryExportPage: type: object properties: items: type: array items: { $ref: '#/components/schemas/AdvisoryExportItem' } nextPageToken: { type: string } SbomExportPage: type: object properties: items: type: array items: { $ref: '#/components/schemas/SbomExportItem' } nextPageToken: { type: string } AttestationExportItem: type: object properties: attestationId: { type: string } artifactId: { type: string } findingId: { type: string } verificationStatus: { type: string } verificationTime: { type: string, format: date-time } dsseDigest: { type: string } rekorEntryId: { type: string } evidenceBundleRef: { type: string } ledgerEventId: { type: string, format: uuid } recordedAt: { type: string, format: date-time } merkleLeafHash: { type: string } rootHash: { type: string } AttestationExportPage: type: object properties: items: type: array items: { $ref: '#/components/schemas/AttestationExportItem' } nextPageToken: { type: string }