# Findings Ledger Export HTTP Surface Prep task: PREP-LEDGER-EXPORT-35-001-NO-HTTP-API-SURFACE (Sprint 0121) ## Goals - Publish an HTTP surface for deterministic, offline-friendly exports of Findings Ledger data (findings, VEX, advisories, SBOMs) so downstream SDK/OpenAPI tasks can proceed. - Define filter contract, pagination, media types, and provenance fields required by Evidence Locker and Policy Engine consumers. ## Non-goals - Implementing the endpoints (covered by LEDGER-EXPORT-35-001). - Final OAS/SDK generation (tracked in PREP-LEDGER-OAS-61-001/002/62-001/63-001). ## Base Service - Host: findings-ledger service (minimal API) under `src/Findings/StellaOps.Findings.Ledger`. - Base path: `/ledger/export`. - Auth: service-to-service bearer token; require `scope=ledger.export.read`. - Tenancy: `X-Stella-Tenant` header (strictly required); responses never mix tenants. - Determinism: server sorts by `(event_sequence, projection_version, cycle_hash)`; pagination tokens encode the last emitted tuple and filter set. No wall-clock dependence. - Media types: default `application/x-ndjson`; clients may request `application/json` (array) for small result sets. Always emit `Content-Encoding: gzip` when `Accept-Encoding` allows. ## Endpoints ### 1) Findings - `GET /ledger/export/findings` - Filters (all optional unless noted): - `shape` (required): `canonical` \| `compact` (controls payload shape; compact strips verbose provenance fields for air-gap bundles) - `since_sequence` (long, ≥0), `until_sequence` (long, inclusive) — enables range slicing. - `since_observed_at`, `until_observed_at` (ISO-8601 UTC) — observation window. - `advisory_id` (repeatable), `component_purl` (repeatable), `finding_status` (`open|fixed|dismissed`), `severity` (`critical|high|medium|low|unknown`). - `risk_profile_version` (string) — filters by attached risk profile revision. - Response item (canonical shape): - `finding_id`, `event_sequence`, `observed_at`, `component` (purl, version, source), `advisories` (ids, cwes), `status`, `severity`, `risk` (score, severity, profile_version, explanation_id), `projection_version`, `cycle_hash`, `evidence_bundle_ref` (digest, dsse_digest, timeline_ref), `provenance` (ledger_root, projector_version, policy_version, datasource_ids). ### 2) VEX - `GET /ledger/export/vex` - Filters: `shape`, `since_sequence/until_sequence`, `since_observed_at/until_observed_at`, `product_id` (repeatable), `advisory_id`, `status` (`affected|not_affected|under_investigation`), `statement_type` (`exploitation|justification`). - Response item adds `vex_statement_id`, `product` (purl or CPE), `status_justification`, `known_exploited` (bool), `timestamp`, `projection_version`, `cycle_hash`, `provenance`. ### 3) Advisories - `GET /ledger/export/advisories` - Filters: `shape`, `since_sequence/until_sequence`, `severity`, `source` (feed id), `cwe_id`, `kev` (bool), `cvss_version`, `cvss_score_min`, `cvss_score_max`. - Response item: `advisory_id`, `source`, `title`, `description`, `cwes`, `cvss` (version, vector, base_score), `published`, `modified`, `status`, `epss` (score, percentile), `projection_version`, `cycle_hash`, `provenance`. ### 4) SBOMs - `GET /ledger/export/sboms` - Filters: `shape`, `since_sequence/until_sequence`, `since_observed_at/until_observed_at`, `subject_digest` (OCI digest), `sbom_format` (`spdx-json` \| `cyclonedx-json`), `component_purl` (repeatable), `contains_native` (bool), `slsa_build_type` (string). - Response item: `sbom_id`, `subject` (digest, media_type), `sbom_format`, `created_at`, `components_count`, `has_vulnerabilities` (bool), `materials` (digests), `projection_version`, `cycle_hash`, `provenance`. ## Pagination - Cursor param: `page_token` (opaque base64url JSON: `{ "last": { "event_sequence": long, "projection_version": string, "cycle_hash": string }, "filters_hash": sha256 }`). - Page size: `page_size` (default 500, max 5000). Server rejects if `page_size` differs across pages for same token. - Response envelope (for both NDJSON and JSON array): - Header `X-Stella-Next-Page-Token` when more data exists. - Header `X-Stella-Result-Count` with items count. - When `Prefer: return=minimal` is set, omit envelope body for NDJSON; clients rely on headers. ## Error Contract - 400: unknown filter, invalid range, or filter combination mismatch with `filters_hash` inside `page_token`. - 401/403: missing or insufficient scope. - 409: requested `shape` not compatible with downstream air-gap mode (compact requested where policy mandates canonical). - 429: enforcement of determinism guard (server detected projection drift vs cycle_hash); include `X-Stella-Drift-Reason`. ## Observability & Determinism Hooks - Emit structured logs `ledger.export.request` and `ledger.export.emit` with tenant, endpoint, filters_hash, page_size, result_count, duration_ms. - Counters: `ledger_export_items_total{endpoint,tenant}` and `ledger_export_failures_total{endpoint,reason}`. - Traces: span name `ledger.export.{endpoint}`; attach `filters_hash`, `page_size`, `next_page_token_present` attributes. ## artefact location - This document: `docs/modules/findings-ledger/export-http-surface.md` (hash stability: keep deterministic ordering as authored on 2025-11-20). - Link from sprint 0121 PREP-LEDGER-EXPORT-35-001; downstream tasks should reference this path for contract details.