Score Proofs & Reachability API Reference
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/Attestor/.../StellaOps.Attestor.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 |
| Attestor proof chain | /api/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/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):
| Policy | Accepts scope(s) |
|---|---|
attestor:read | attestor.read, attestor.verify, attestor.write |
attestor:verify | attestor.verify, attestor.write |
attestor:write | attestor.write |
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
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).
Correction (2026-05-29): The wire shape is
ScanManifestResponsewithsbomHash,rulesHash,feedHash,policyHash(plus versions/timestamps and an RFC 9530contentDigest). TheconcelierSnapshotHash/excititorSnapshotHash/latticePolicyHash/seed/knobsfields and the nestedmanifest+dsseEnvelopeJSON envelope are only present in the DSSE negotiated form (SignedScanManifestResponse), not the defaultapplication/jsonbody.
Response (200 OK, application/json)
{
"manifestId": "manifest-001",
"scanId": "550e8400-e29b-41d4-a716-446655440000",
"manifestHash": "sha256:manifest123...",
"sbomHash": "sha256:sbom...",
"rulesHash": "sha256:rules...",
"feedHash": "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 response is a flatScoreReplayResponsewith a factorised score, not a nestedscoreProof.nodes[]ledger.
Request (ScoreReplayRequest)
{ "manifestHash": "sha256:manifest123...", "freezeTimestamp": "2025-12-17T13:00:00Z" }
Response (200 OK, ScoreReplayResponse)
{
"score": 0.50,
"rootHash": "sha256:proof123...",
"bundleUri": "/api/v1/scans/.../score/bundle?rootHash=sha256:proof123...",
"manifestHash": "sha256:manifest123...",
"canonicalInputHash": "sha256:cinput...",
"factors": [ { "name": "cvss_base", "weight": 0.5, "raw": 9.1, "weighted": 0.50, "source": "cvss" } ],
"verificationStatus": "verified",
"replayedAt": "2025-12-17T13:00:00Z",
"deterministic": true
}
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 previously documented
/{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 previously documented
/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/Attestor/.../StellaOps.Attestor.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 scopesattestor.write/attestor.read/attestor.verify), notproofs.*/anchors.manage.The implemented proof-chain surface is
ProofChainControllerat/api/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: Controllers/ProofChainController.cs, route /api/v1/proofs, scope attestor:read (verify route additionally attestor:verify). Backed by IProofChainQueryService + IProofVerificationService (real implementations).
| Method | Route | Description |
|---|---|---|
| GET | /api/v1/proofs/{subjectDigest} | List proofs for an artifact (ProofListResponse) |
| GET | /api/v1/proofs/{subjectDigest}/chain?maxDepth= | Evidence-chain graph (ProofChainResponse, depth 1–10, default 5) |
| GET | /api/v1/proofs/id/{proofId} | Proof detail (ProofDetail) |
| GET | /api/v1/proofs/id/{proofId}/verify | Verify a proof (ProofVerificationResult; scope attestor:verify) |
A related implemented controller, Controllers/ChainController.cs at /api/v1/chains, exposes attestation-chain traversal (/{attestationId}, /{attestationId}/upstream, /{attestationId}/downstream, /{attestationId}/graph?format=mermaid|dot|json, /artifact/{artifactDigest}?chain=).
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;
rulesHash: string;
feedHash: 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 the Attestor implemented proof-chain controller by artifact subject digest
curl "https://attestor.example.com/api/v1/proofs/sha256:abc123..." \
-H "Authorization: Bearer $TOKEN" # scope: attestor.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
