Score Proofs & Reachability API Reference
Evidence host and authorization currency (source-verified 2026-10-07 at
12a1bad830e4528b5ace9506a325a675e96782c6):src/Evidence/StellaOps.Evidence.WebService/composes the current application libraries. Implemented subject-digest proof queries use the four canonical GET operations under/api/evidence/v1/proofs. The landed PCD-443 change withdraws the predecessor query alias; the accepted source contract preserves the canonical four GET methods. Root-relative proof/anchor/verify controllers remain 501 scaffolding.attestor:writerequiresattest:create;attestor:readandattestor:verifyacceptattest:readorattest:create. Trusted-network bypass is removed. No runtime route or authentication probe was performed.
Version: 1.0.0 Sprint: 3500.0004.0004 Status: Reconciled against implementation 2026-05-29 (was “Complete”)
This document is the API reference for the Score Proofs, Reachability Analysis, and Unknowns management features, spanning the Scanner WebService and the Attestor proof-chain surface. It is written for backend integrators and CI pipelines wiring these endpoints into release gates.
Reconciliation note (2026-05-29): Routes and scopes were corrected against
src/Scanner/StellaOps.Scanner.WebService/andsrc/Evidence/StellaOps.Evidence.WebService/. Two classes of error were fixed: (1) Scanner endpoints are mounted under base path/api/v1(not/api/v1/scanner), and (2) several documented endpoints had no backing controller or are 501 stubs. See the inline correction blocks and §6 Proof Chain API.
Table of Contents
- Overview
- Authentication
- Score Proofs API
- Reachability API
- Unknowns API
- Proof Chain API
- Data Models
- Error Handling
- Rate Limiting
- Examples
1. Overview
Design Principles
- Deterministic: All outputs use canonical JSON serialization (RFC 8785/JCS)
- Verifiable: DSSE signatures on all proof artifacts
- Idempotent:
Content-Digestheaders enable safe retries - Offline-First: All bundles downloadable for air-gap verification
Base URLs
| Service | Base URL | Description |
|---|---|---|
| Scanner | /api/v1 (default; scanner:Api:BasePath) | Scans, manifests, score replay, reachability, unknowns |
| Evidence proof chain | /api/evidence/v1/proofs (ProofChainController, implemented) | Proof-by-subject query + verify |
| Attestor proof spine | /proofs, /anchors, /verify (stubs, return 501) | Spine/receipt/anchor/verify scaffolding |
Correction (2026-05-29): Scanner is
/api/v1, not/api/v1/scanner. Unknowns are a Scanner route group at/api/v1/unknowns. The Attestor exposes a real proof-chain query controller at/api/evidence/v1/proofs/{subjectDigest}and a separate, not-yet-implemented spine/anchor/verify surface at root-relative/proofs/*,/anchors/*,/verify/*— see §6.
Supported Content Types
| Type | Description |
|---|---|
application/json | Standard JSON (responses are canonical) |
application/x-ndjson | Streaming NDJSON for large call graphs |
application/zip | Proof bundle archives |
2. Authentication
All endpoints require OAuth 2.0 Bearer token authentication.
Token Request
POST /connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&
client_id=ci-bot&
client_secret=REDACTED&
scope=scanner.scans.read scanner.scans.write scanner.callgraph.ingest
Response
{
"access_token": "eyJraWQi...",
"token_type": "Bearer",
"expires_in": 3600
}
Required Scopes
Correction (2026-05-29): The scopes
scanner.scans,scanner.proofs,scanner.unknowns,scanner.unknowns:write,proofs.read,proofs.write,proofs.verify, andanchors.managedo not exist anywhere insrc/. The real Scanner scope policies (Security/ScannerAuthorityScopes.cs, wired inProgram.cs) and the real Attestor authorization policies (AttestorWebServiceComposition.cs) are below.
Scanner (read paths accept the canonical scanner:read; write paths accept scanner:write):
| Scope | Grants |
|---|---|
scanner.scans.read (or scanner:read) | Read scans, manifests, proof bundles, reachability, unknowns |
scanner.scans.write (or scanner:write) | Score replay/verify, compute reachability |
scanner.scans.enqueue (or scanner:scan/scanner:write) | Submit scans |
scanner.callgraph.ingest | Upload call graphs |
Attestor proof chain (policy → required JWT scope; see §6):
Correction (2026-09-05, SPRINT_20260904_003 ROA-3): the
attestor.read/attestor.verify/attestor.writescopes in the table this replaces do not exist in the Authority catalog and were granted to no client, so a token minted from them satisfied none of these policies. The policy NAMES below are internal labels the endpoints attach by literal; the CLAIMS they require are the canonical catalog scopes. Verified againstsrc/Evidence/__Libraries/StellaOps.Attestor.Application/Security/AttestorPolicies.cs(AddAttestorPolicies) andStellaOpsScopes; re-verify withdotnet test src/Evidence/__Tests/StellaOps.Evidence.WebService.Tests/StellaOps.Evidence.WebService.Tests.csproj(classAttestorPolicyClaimPublicationTests, which resolves each policy from the real host).
| Policy | Accepts scope(s) |
|---|---|
attestor:read | attest:read, attest:create |
attestor:verify | attest:read, attest:create (an inclusion check is read-only) |
attestor:write | attest:create |
Requests arriving from inside the estate’s trusted networks with no Authorization header are additionally authorized on these three policies by a service bypass (Authority:ResourceServer:BypassNetworks). That branch is a direct-path capability only: the gateway refuses to forward a request without a token, so nothing routed through it can reach the bypass.
3. Score Proofs API
3.1 Submit Scan
POST /api/v1/scans — Endpoints/ScanEndpoints.cs (scanner.scans.submit)
Correction (2026-05-29): This submits a scan for an OCI artifact (image reference and/or digest) and returns 202 Accepted; it does not create a pre-built manifest. The
201 Createdmanifest-creation contract with feed/VEX/policy snapshot hashes was not implemented. The manifest is read back via §3.2.
Request (ScanSubmitRequest)
{
"image": { "reference": "registry.example.com/myapp:1.0.0", "digest": "sha256:abc123..." },
"metadata": { "env": "stage" }
}
Response (202 Accepted, ScanSubmitResponse)
{
"scanId": "550e8400-e29b-41d4-a716-446655440000",
"status": "Pending",
"location": "/api/v1/scans/550e8400-e29b-41d4-a716-446655440000",
"created": true
}
Errors
| Code | Description |
|---|---|
| 400 | Neither image reference nor digest supplied, or invalid digest prefix |
| 409 | Duplicate submission |
3.2 Get Scan Manifest
New signed manifests require schemaVersion: 2 and DSSE payload type scanmanifest.stella/v2. The forward migration renames the input columns without rewriting stored hashes or JSON. Historical v1 signed artifacts retain their original bytes, digest and signature: generic DSSE verification remains available, but typed replay and DSSE manifest retrieval reject v1 (422, scan_manifest_schema_unsupported) before re-signing. Download and retain historical proof bundles as raw artifacts; obtaining a v2 replay requires a new scan. No field aliases or automatic signature conversion are provided. SHA-256 content addresses and RFC 9530 Content-Digest remain fixed wire contracts, computed through the central cryptography library.
GET /api/v1/scans/{scanId}/manifest — Endpoints/ManifestEndpoints.cs (scanner.scans.manifest)
Returns the manifest with canonical input hashes. Send Accept: application/dsse+json for the DSSE-signed envelope. Rate-limited (ManifestPolicy).
Verified manifest contract (2026-10-05, PCD-303): The plain response is
ScanManifestResponsewithsbomHash,vexSnapshotHash,vulnerabilitiesSnapshotHash,policyHash, versions/timestamps and RFC 9530contentDigest. The negotiated DSSE form (SignedScanManifestResponse) adds the nestedmanifestanddsseEnvelope; the typed manifest also carriesschemaVersion,latticePolicyHash,seedandknobs.
Response (200 OK, application/json)
{
"manifestId": "manifest-001",
"scanId": "550e8400-e29b-41d4-a716-446655440000",
"manifestHash": "sha256:manifest123...",
"sbomHash": "sha256:sbom...",
"vexSnapshotHash": "sha256:rules...",
"vulnerabilitiesSnapshotHash": "sha256:feed...",
"policyHash": "sha256:policy...",
"scannerVersion": "1.0.0",
"createdAt": "2025-12-17T12:00:00Z",
"contentDigest": "sha-256=:<base64>:"
}
3.3 Replay Score Computation
POST /api/v1/scans/{scanId}/score/replay — Endpoints/ScoreReplayEndpoints.cs (scanner.scans.score.replay). Registered only when scanner:ScoreReplay:Enabled is true.
Correction (2026-05-29): The request is
ScoreReplayRequest(manifestHash,freezeTimestamp— both optional); there is nooverrides/snapshot-substitution body. The stored success contract is a flatScoreReplayResponse, rather than a nestedscoreProof.nodes[]ledger. Default replay currently refuses missing measurements.
PCD-403 (2026-10-06): a signed v2 manifest supplies immutable addresses without attributable frozen CVSS/EPSS/reachability/provenance values. Default scoring returns 422 with stable code scanner.score_replay.measurements_unavailable, before writing scoring nodes, a proof bundle or history. Generic knobs and digest bytes cannot provide those measurements. Stored bundle verification remains available and proves artifact integrity rather than metric semantics. See the detailed contract.
Request (ScoreReplayRequest)
{ "manifestHash": "sha256:manifest123...", "freezeTimestamp": "2025-12-17T13:00:00Z" }
Response (422 Unprocessable Entity, current default)
{
"status": 422,
"code": "scanner.score_replay.measurements_unavailable",
"detail": "scanner.score_replay.measurements_unavailable: snapshot addresses do not provide attributable frozen CVSS, EPSS, reachability or provenance measurements."
}
Sibling routes (same group): GET /score/bundle?rootHash=..., POST /score/verify, GET /score/history.
3.4 Fetch Proof Bundle Metadata
GET /api/v1/scans/{scanId}/proofs/{rootHash} — Endpoints/ManifestEndpoints.cs (scanner.scans.proofs.get)
Correction (2026-05-29): This returns a JSON
ProofBundleResponse(hashes + signature metadata), not anapplication/ziparchive. There are noContent-Disposition/X-Proof-Root-Hash/X-Manifest-Hashheaders and no bundle-contents file manifest. A siblingGET /api/v1/scans/{scanId}/proofslists bundles for a scan.
Response (200 OK, application/json, ProofBundleResponse)
{
"scanId": "550e8400-e29b-41d4-a716-446655440000",
"rootHash": "sha256:proof123...",
"bundleType": "score-proof",
"bundleHash": "sha256:bundle...",
"manifestHash": "sha256:manifest...",
"signatureKeyId": "ecdsa-p256-key-001",
"signatureAlgorithm": "ES256",
"createdAt": "2025-12-17T12:00:00Z",
"signatureValid": true,
"contentDigest": "sha-256=:<base64>:"
}
4. Reachability API
4.1 Upload Call Graph
POST /api/v1/scans/{scanId}/callgraphs — Endpoints/CallGraphEndpoints.cs (scanner.scans.callgraphs.submit). Scope: scanner.callgraph.ingest. Content-Digest header required.
Correction (2026-05-29): A top-level
scanKeyfield is required, schema must be exactlystella.callgraph.v1, and the response isCallGraphAcceptedResponseDto(callgraphId,nodeCount,edgeCount,digest) — noscanId/entrypointsCount/status. A duplicateContent-Digestreturns409 Conflict.
Request (CallGraphV1Dto)
{
"schema": "stella.callgraph.v1",
"scanKey": "550e8400-e29b-41d4-a716-446655440000",
"language": "dotnet",
"artifacts": [
{
"artifactKey": "MyApp.WebApi.dll",
"kind": "assembly",
"sha256": "sha256:artifact123..."
}
],
"nodes": [
{
"nodeId": "sha256:node1...",
"artifactKey": "MyApp.WebApi.dll",
"symbolKey": "MyApp.Controllers.OrdersController::Get(System.Guid)",
"visibility": "public",
"isEntrypointCandidate": true
}
],
"edges": [
{
"from": "sha256:node1...",
"to": "sha256:node2...",
"kind": "static",
"reason": "direct_call",
"weight": 1.0
}
],
"entrypoints": [
{
"nodeId": "sha256:node1...",
"kind": "http",
"route": "/api/orders/{id}",
"framework": "aspnetcore"
}
]
}
Response (202 Accepted, CallGraphAcceptedResponseDto)
{ "callgraphId": "cg-001", "nodeCount": 1234, "edgeCount": 5678, "digest": "sha256:cg123..." }
4.2 Compute Reachability
POST /api/v1/scans/{scanId}/compute-reachability — Endpoints/ReachabilityEndpoints.cs (scanner.scans.compute-reachability). Scope: scanner.scans.write.
Correction (2026-05-29): The route is
/scans/{scanId}/compute-reachability, not/scans/{scanId}/reachability/compute. Response isComputeReachabilityResponseDto(jobId,status,estimatedDuration); noscanId/_links. The default wiring registers aNullReachabilityComputeService, so without a reachability runtime the endpoint returns 501 Not Implemented. There is no documented Scanner/jobs/{jobId}polling route.
Response (202 Accepted, ComputeReachabilityResponseDto)
{ "jobId": "reachability-job-001", "status": "queued", "estimatedDuration": "30s" }
4.3 Get Reachability Findings
GET /api/v1/scans/{scanId}/reachability/findings — Endpoints/ReachabilityEndpoints.cs (scanner.scans.reachability.findings)
Correction (2026-05-29): Query params are
cveandstatus(notcveId). Response isReachabilityFindingListDtowith a flatitems[]andtotal; there is noscanId,computedAt, per-findingpath/evidence, nor asummaryaggregate. A siblingGET /reachability/componentsreturnsComponentReachabilityListDto.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
cve | string | Filter by CVE ID |
status | string | Filter by reachability status |
Response (200 OK, ReachabilityFindingListDto)
{
"items": [
{
"cveId": "CVE-2024-1234",
"purl": "pkg:npm/lodash@4.17.20",
"status": "reachable",
"confidence": 0.70,
"latticeState": "REACHABLE_STATIC",
"severity": "high",
"affectedVersions": ["<4.17.21"]
}
],
"total": 1
}
4.4 Explain Reachability
GET /api/v1/scans/{scanId}/reachability/explain — Endpoints/ReachabilityEndpoints.cs (scanner.scans.reachability.explain)
Correction (2026-05-29): Response is
ReachabilityExplanationDto. The path is apathWitnessstring list, reasons are awhy[]list of{code, description, impact}, and evidence is grouped underevidence(staticAnalysis,runtimeEvidence,policyEvaluation) plus an optionalspineId. There is noexplanation.shortestPath[],confidenceFactors, oralternativePaths. Missingcve/purlreturns 400.
Query Parameters
| Parameter | Required | Description |
|---|---|---|
cve | Yes | CVE ID |
purl | Yes | Package URL |
Response (200 OK, ReachabilityExplanationDto)
{
"cveId": "CVE-2024-1234",
"purl": "pkg:npm/lodash@4.17.20",
"status": "reachable",
"confidence": 0.70,
"latticeState": "REACHABLE_STATIC",
"pathWitness": ["OrdersController::Get -> OrderService::Process -> Lodash.merge"],
"why": [ { "code": "static_path", "description": "Static call path exists", "impact": "positive" } ],
"evidence": {
"staticAnalysis": { "callgraphDigest": "sha256:cg123...", "pathLength": 3, "edgeTypes": ["static"] },
"runtimeEvidence": null,
"policyEvaluation": { "policyDigest": "sha256:policy...", "verdict": "blocked", "verdictReason": "reachable_critical" }
},
"spineId": "spine-001"
}
5. Unknowns API
Correction (2026-05-29): The Unknowns API is a Scanner route group (
Endpoints/UnknownsEndpoints.cs, allscanner.scans.read). It is read-only: there is no escalate, resolve, proof, or summary endpoint. The implemented routes are:GET /unknowns,/unknowns/stats,/unknowns/bands,/unknowns/{id},/unknowns/{id}/evidence,/unknowns/{id}/history. The rich blast-radius / containment / exploit-pressure / score-breakdown / reason-details fields below were not implemented.
5.1 List Unknowns
GET /api/v1/unknowns — scanner.unknowns.list
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
band | string | — | Filter: HOT, WARM, COLD |
artifactDigest | string | — | Filter by artifact digest |
vulnId | string | — | Filter by vulnerability ID |
sortBy | string | score | score, created, updated |
sortOrder | string | desc | asc, desc |
limit | int | 50 | Clamped 1–500 |
offset | int | 0 | Pagination offset |
Response (200 OK, UnknownsListResponse)
{
"items": [
{
"id": "unk-3f1c...",
"artifactDigest": "sha256:abc123...",
"vulnerabilityId": "CVE-2024-1234",
"packagePurl": "pkg:npm/lodash@4.17.20",
"score": 0.62,
"band": "WARM",
"createdAtUtc": "2025-01-15T10:30:00Z",
"updatedAtUtc": "2025-01-15T10:30:00Z"
}
],
"totalCount": 142,
"limit": 50,
"offset": 0
}
5.2 Get Unknown by ID
GET /api/v1/unknowns/{id} — scanner.unknowns.get
Returns UnknownDetailResponse (id, artifactDigest, vulnerabilityId, packagePurl, score, band, proofRef, createdAtUtc, updatedAtUtc). The id accepts either a GUID or the external unk-<32hex> form.
{
"id": "unk-3f1c...",
"artifactDigest": "sha256:abc123...",
"vulnerabilityId": "CVE-2024-1234",
"packagePurl": "pkg:npm/lodash@4.17.20",
"score": 0.62,
"band": "WARM",
"proofRef": "proofs/unknowns/unk-3f1c/tree.json",
"createdAtUtc": "2025-01-15T10:30:00Z",
"updatedAtUtc": "2025-01-15T10:30:00Z"
}
5.3 Get Unknown Evidence
GET /api/v1/unknowns/{id}/evidence — scanner.unknowns.evidence
Replaces the
/{id}/proof(which does not exist). ReturnsUnknownEvidenceResponse.
{ "id": "unk-3f1c...", "proofRef": "proofs/unknowns/unk-3f1c/tree.json", "lastUpdatedAtUtc": "2025-01-15T10:30:00Z" }
5.4 Get Unknown History
GET /api/v1/unknowns/{id}/history — scanner.unknowns.history
Returns UnknownHistoryResponse (id, history[] of {capturedAtUtc, score, band}).
5.5 Unknowns Stats
GET /api/v1/unknowns/stats — scanner.unknowns.stats
Replaces the
/unknowns/summary(which does not exist). ReturnsUnknownsStatsResponse.
{ "total": 142, "hot": 12, "warm": 48, "cold": 82 }
GET /api/v1/unknowns/bands (scanner.unknowns.bands) returns a band → count distribution map.
5.6 Escalate / Resolve — NOT IMPLEMENTED
Removed (2026-05-29):
POST /unknowns/{id}/escalateandPOST /unknowns/{id}/resolvehave no backing route inUnknownsEndpoints.cs(the group exposes only theGETroutes above). File a sprint to add write routes + audit actions before re-documenting these.
6. Proof Chain API
Major correction (2026-05-29): The Attestor (
src/Evidence/StellaOps.Evidence.WebService/) hosts two distinct proof surfaces. The{entry}/spine,{entry}/receipt,{entry}/vex, trust-anchor, and verify endpoints documented here are part of the spine/anchor/verify scaffolding (ProofsControllerat routeproofs,AnchorsControlleratanchors,VerifyControlleratverify— all root-relative, no/api/v1prefix). Every handler in that scaffolding returns 501 Not Implemented, gated behind feature flags (AnchorsEnabled/ProofsEnabled/VerifyEnabled, all defaultfalse) — and even when the flag is on, the body still returns 501. They are forward-looking stubs, not live endpoints. The authorization policies areattestor:write/attestor:read/attestor:verify(requiring scopesattest:createfor write, andattest:readorattest:createfor read/verify), notproofs.*/anchors.manage.The implemented proof-chain surface is
ProofChainControllerat/api/evidence/v1/proofs, which is keyed by artifact subject digest rather than an SBOM-entry composite — see §6.5.
6.1 Create Proof Spine — NOT IMPLEMENTED (501 stub)
POST /proofs/{entry}/spine — Controllers/ProofsController.cs (scope attestor:write). Returns 501. {entry} format: sha256:<hash>:pkg:<purl>. Documented request (evidenceIds, reasoningId, vexVerdictId, policyVersion) and 201 response (proofBundleId, receiptUrl) describe the intended contract once implemented.
6.2 Get Proof Spine — NOT IMPLEMENTED (501 stub)
GET /proofs/{entry}/spine — ProofsController (scope attestor:read). Returns 501.
6.3 Get Verification Receipt — NOT IMPLEMENTED (501 stub)
GET /proofs/{entry}/receipt — ProofsController (scope attestor:read). Returns 501. A GET /proofs/{entry}/vex stub also exists (scope attestor:read, 501).
6.4 Verify Proof Bundle — NOT IMPLEMENTED (501 stub)
POST /verify/{proofBundleId} — Controllers/VerifyController.cs (scope attestor:verify). Returns 501.
Correction: the bundle id is a path parameter (
/verify/{proofBundleId}), not a body field, and there is noPOST /verify(orPOST /proofs/verify) collection route and noPOST /verify/batch. Additional stubs:GET /verify/envelope/{envelopeHash}andGET /verify/rekor/{envelopeHash}(both scopeattestor:read, 501). Trust-anchor stubs live onControllers/AnchorsController.cs:GET /anchors,GET /anchors/{anchorId},POST /anchors,PATCH /anchors/{anchorId:guid},POST /anchors/{anchorId:guid}/revoke-key,DELETE /anchors/{anchorId:guid}— all 501. (Note revocation isPOST.../revoke-key, not a plainDELETE.)
The documented verify request/response shape (checkRekor, anchorIds, checks.{signatureValid, idRecomputed, merklePathValid, rekorInclusionValid}) and the four-step verification flow remain the target contract for these stubs.
6.5 Implemented Proof-Chain Query Controller
Source: src/Evidence/__Libraries/StellaOps.Attestor.Application/Controllers/ProofChainController.cs, route /api/evidence/v1/proofs, policy attestor:read (canonical scope attest:read or attest:create; verify additionally requires the attestor:verify policy). The predecessor query alias is withdrawn. Re-verify the four GET attributes with:
rg -n 'Route\(|HttpGet' src/Evidence/__Libraries/StellaOps.Attestor.Application/Controllers/ProofChainController.cs
| Method | Route | Description |
|---|---|---|
| GET | /api/evidence/v1/proofs/{subjectDigest} | List proofs for an artifact (ProofListResponse) |
| GET | /api/evidence/v1/proofs/{subjectDigest}/chain?maxDepth= | Evidence-chain graph (ProofChainResponse, depth 1–10, default 5) |
| GET | /api/evidence/v1/proofs/id/{proofId} | Proof detail (ProofDetail) |
| GET | /api/evidence/v1/proofs/id/{proofId}/verify | Verify a proof (ProofVerificationResult; scope attestor:verify) |
The related ChainController traversal declarations are a prototype excluded by src/Evidence/__Libraries/StellaOps.Attestor.Application/AttestorWebServiceControllerFeatureProvider.cs; they do not establish a served /api/v1/chains contract.
7. Data Models
ScanManifestResponse (wire shape)
Correction (2026-05-29): This is the actual
application/jsonshape returned byGET /scans/{scanId}/manifest(Contracts/ManifestContracts.cs). The internalStellaOps.Scanner.Core/ScanManifest.csmodel has additional fields (incl.latticePolicyHash,seed,knobs) but they are not on this response DTO.
interface ScanManifestResponse {
manifestId: string;
scanId: string;
manifestHash: string; // sha256:...
sbomHash: string;
vexSnapshotHash: string;
vulnerabilitiesSnapshotHash: string;
policyHash: string;
scanStartedAt: string | null; // ISO 8601
scanCompletedAt: string | null;
scannerVersion: string;
createdAt: string;
contentDigest: string; // RFC 9530, sha-256=:...:
}
ProofNode
interface ProofNode {
id: string;
kind: "Input" | "Transform" | "Delta" | "Score";
ruleId: string;
parentIds: string[];
evidenceRefs: string[];
delta: number;
total: number;
actor: string;
tsUtc: string;
seed: string;
nodeHash: string;
}
DsseEnvelope
interface DsseEnvelope {
payloadType: string;
payload: string; // base64 canonical JSON
signatures: DsseSignature[];
}
interface DsseSignature {
keyid: string;
sig: string; // base64
}
ReachabilityStatus
enum ReachabilityStatus {
UNREACHABLE = "UNREACHABLE",
POSSIBLY_REACHABLE = "POSSIBLY_REACHABLE",
REACHABLE_STATIC = "REACHABLE_STATIC",
REACHABLE_PROVEN = "REACHABLE_PROVEN",
UNKNOWN = "UNKNOWN"
}
UnknownReasonCode
| Code | Description |
|---|---|
missing_vex | No VEX statement for vulnerability |
ambiguous_indirect_call | Indirect call target unresolved |
incomplete_sbom | SBOM missing component data |
unknown_platform | Platform not recognized |
missing_advisory | No advisory data for CVE |
conflicting_evidence | Multiple conflicting data sources |
stale_data | Data exceeds freshness threshold |
8. Error Handling
All errors follow RFC 7807 Problem Details format.
Error Response
{
"type": "https://stella-ops.org/errors/scan-not-found",
"title": "Scan Not Found",
"status": 404,
"detail": "Scan ID '550e8400...' does not exist.",
"instance": "/api/v1/scans/550e8400...",
"traceId": "trace-001"
}
Error Types
Note (2026-05-29): Problem responses are built from localization keys, not a fixed slug registry. The
invalid-manifest,duplicate-scan,snapshot-not-found,callgraph-not-uploaded,unknown-not-found, andescalation-conflictrows belong to the removed manifest-creation / escalate contracts and do not map to live endpoints.
| Type (illustrative) | Status | Description |
|---|---|---|
scan-not-found | 404 | Scan ID not found |
invalid-callgraph | 400 | Call-graph schema validation failed |
callgraph-duplicate | 409 | Duplicate call-graph (Content-Digest) |
payload-too-large | 413 | Request body exceeds size limit |
proof-not-found | 404 | Proof root hash not found |
reachability-in-progress | 409 | Reachability already running |
reachability-unavailable | 501 | Reachability runtime not configured |
9. Rate Limiting
Correction (2026-05-29): On the Scanner side, only
GET /scans/{scanId}/manifestis rate-limited in code (RequireRateLimiting(ManifestPolicy)). The per-endpoint quotas previously listed for submit/replay/callgraphs/reachability/unknowns are not enforced and were aspirational. The Attestor proof-chain controllers use named rate-limiter policies (attestor-reads,attestor-submissions,attestor-verifications).
Response Headers
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per window |
X-RateLimit-Remaining | Remaining requests |
X-RateLimit-Reset | Unix timestamp when limit resets |
Rate Limit Error (429)
{
"type": "https://stella-ops.org/errors/rate-limit-exceeded",
"title": "Rate Limit Exceeded",
"status": 429,
"detail": "Exceeded 100 requests/hour for POST /scans.",
"retryAfter": 1234567890
}
10. Examples
Example 1: Complete Scan Workflow
# 1. Submit scan (image reference and/or digest)
SCAN_RESP=$(curl -X POST https://scanner.example.com/api/v1/scans \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "image": { "reference": "registry.example.com/myapp:1.0.0", "digest": "sha256:abc123..." } }')
SCAN_ID=$(echo $SCAN_RESP | jq -r '.scanId')
# 2. Upload call graph (Content-Digest header is required)
curl -X POST "https://scanner.example.com/api/v1/scans/$SCAN_ID/callgraphs" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Content-Digest: sha256=abc123..." \
-d @callgraph.json
# 3. Compute reachability (501 if no reachability runtime configured)
curl -X POST "https://scanner.example.com/api/v1/scans/$SCAN_ID/compute-reachability" \
-H "Authorization: Bearer $TOKEN"
# 4. Get findings
curl "https://scanner.example.com/api/v1/scans/$SCAN_ID/reachability/findings" \
-H "Authorization: Bearer $TOKEN"
Example 2: Score Replay
# Replay deterministically against a frozen manifest (requires scanner:ScoreReplay:Enabled)
curl -X POST "https://scanner.example.com/api/v1/scans/$SCAN_ID/score/replay" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "manifestHash": "sha256:manifest123..." }'
Example 3: Unknowns (read-only)
# List HOT-band unknowns, score-sorted descending
curl "https://scanner.example.com/api/v1/unknowns?band=HOT&sortBy=score&sortOrder=desc" \
-H "Authorization: Bearer $TOKEN"
# Aggregate counts
curl "https://scanner.example.com/api/v1/unknowns/stats" -H "Authorization: Bearer $TOKEN"
Example 4: Proof Bundle Metadata + Proof-Chain Query
# Fetch proof bundle metadata (JSON, not a zip)
curl "https://scanner.example.com/api/v1/scans/$SCAN_ID/proofs/sha256:proof123..." \
-H "Authorization: Bearer $TOKEN"
# Query Evidence by a complete sha256:<64-hex> artifact subject digest
curl "https://evidence.example.com/api/evidence/v1/proofs/$SUBJECT_DIGEST" \
-H "Authorization: Bearer $TOKEN" # scope: attest:read
# NOTE: the /verify, /proofs/{entry}/spine, and /anchors endpoints are 501 stubs (see §6).
Related Documentation
- Scanner Architecture
- Attestor Architecture
- Policy Engine
- CLI Reference
- Scanner Score Proofs & Reachability API — the same Scanner surface, endpoint-by-endpoint
- CanonJson API Reference — canonicalization behind the content-addressed hashes
- OpenAPI specification:
src/Api/StellaOps.Api.OpenApi/scanner/openapi.yaml(in-repo)
Last Updated: 2026-05-29 (doc↔code reconciliation) API Version: 1.0.0 Sprint: 3500.0004.0004
Proof/verdict retrieval route contract verified against d456ec1a2c (2026-10-07). Re-verify this scoped contract with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/Evidence/__Tests/StellaOps.Evidence.WebService.Tests/StellaOps.Evidence.WebService.Tests.csproj -Class '*EvidenceAliasWithdrawalTests*' -BuildProjectReferences.
