Scanner WebService API — Score Proofs & Reachability Extensions
Version: 2.0 Base URL: /api/v1 (configurable via scanner:Api:BasePath; default /api/v1) Authentication: Bearer token (OpTok with DPoP/mTLS) Sprint: SPRINT_3500_0002_0003, SPRINT_3500_0003_0003
Reconciliation note (2026-05-29): Routes in this document were corrected against
src/Scanner/StellaOps.Scanner.WebService/. The real service mounts every endpoint under thescanner:Api:BasePathgroup (default/api/v1, NOT/api/v1/scanner). Scan resources therefore live at/api/v1/scans/.... See the per-endpoint headers below and the Source Map for the backing endpoint class for each route.
Overview
Audience: Backend integrators and CI pipelines calling the Scanner WebService to submit scans, ingest call graphs, replay deterministic scores, and read reachability and unknowns data.
This document specifies the Scanner.WebService HTTP surface for:
- Scan submission and deterministic manifests / replay
- Score proof bundles
- Call-graph ingestion and reachability analysis
- Unknowns management
Design Principles:
- All endpoints return canonical JSON (deterministic serialization)
- Idempotency via
Content-Digestheaders (SHA-256) - DSSE signatures returned for proof / manifest artifacts
- Offline-first (proof bundles built content-addressed for air-gap verification)
Endpoints
1. Submit Scan
POST /api/v1/scans
Source: Endpoints/ScanEndpoints.cs → scanner.scans.submit. Scope policy: ScansEnqueue (any-of scanner.scans.enqueue, scanner.scans.write, scanner:scan, scanner:write).
Correction (2026-05-29): The implemented submit endpoint does not accept a pre-built manifest with feed/VEX/policy snapshot hashes. It accepts an image reference and/or artifact digest and returns 202 Accepted with a scan id. The deterministic manifest is built by the worker and read back via Endpoint 2; the score-replay flow (Endpoint 3) currently refuses scoring because the manifest does not bind attributable frozen measurements. The
201 Createdmanifest-creation contract did not exist.
Description: Submits a new scan for an OCI artifact.
Request Body (ScanSubmitRequest):
{
"image": {
"reference": "registry.example.com/myapp:1.0.0",
"digest": "sha256:abc123..."
},
"metadata": { "env": "stage" }
}
artifactRef / artifactDigest top-level fields are accepted as aliases for image.reference / image.digest. At least one of reference or digest is required; a supplied digest must be a valid sha256: digest.
Response (202 Accepted, ScanSubmitResponse):
{
"scanId": "550e8400-e29b-41d4-a716-446655440000",
"status": "Pending",
"location": "/api/v1/scans/550e8400-e29b-41d4-a716-446655440000",
"created": true
}
Headers:
Location:/api/v1/scans/{scanId}
Errors:
400 Bad Request— Neither image reference nor digest supplied, or invalid digest prefix409 Conflict— Duplicate submission
Status / progress: GET /api/v1/scans/{scanId} (scanner.scans.status) and GET /api/v1/scans/{scanId}/events (SSE progress stream).
2. Retrieve 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
Source: Endpoints/ManifestEndpoints.cs → scanner.scans.manifest. Scope policy: ScansRead (any-of scanner.scans.read, scanner:read). Rate-limited (ManifestPolicy).
Description: Retrieves the scan manifest with the canonical input hashes used for deterministic replay. Supports content negotiation: send Accept: application/dsse+json to receive the DSSE-signed manifest envelope.
Verified manifest contract (2026-10-05, PCD-303): The plain response projects
sbomHash,vexSnapshotHash,vulnerabilitiesSnapshotHashandpolicyHash. The negotiated DSSE form also carriesschemaVersion,latticePolicyHash,seedandknobs. There is no_linksblock and noETag/If-None-Matchcaching; the plain response carries an RFC 9530Content-Digest(sha-256=:...:).
Response (200 OK, application/json, ScanManifestResponse):
{
"manifestId": "manifest-001",
"scanId": "550e8400-e29b-41d4-a716-446655440000",
"manifestHash": "sha256:manifest123...",
"sbomHash": "sha256:sbom...",
"vexSnapshotHash": "sha256:rules...",
"vulnerabilitiesSnapshotHash": "sha256:feed...",
"policyHash": "sha256:policy...",
"scanStartedAt": "2025-12-17T12:00:00Z",
"scanCompletedAt": "2025-12-17T12:02:00Z",
"scannerVersion": "1.0.0",
"createdAt": "2025-12-17T12:00:00Z",
"contentDigest": "sha-256=:<base64>:"
}
Response (200 OK, application/dsse+json, SignedScanManifestResponse): wraps the manifest above plus manifestHash, a DSSE envelope (payloadType, payload, signatures[]), signedAt, and signatureValid.
Errors:
404 Not Found— Scan ID not found, invalid id format, or manifest not yet produced. A scan of another tenant, or a scan without a persisted owner, returns the same404with no body.429 Too Many Requests— Rate limit exceeded
3. Replay Score Computation
POST /api/v1/scans/{scanId}/score/replay
Source: Endpoints/ScoreReplayEndpoints.cs → scanner.scans.score.replay. Scope policy: ScansWrite (any-of scanner.scans.write, scanner:write). Only registered when scanner:ScoreReplay:Enabled is true.
Correction (2026-05-29): The request body is
ScoreReplayRequestwith optionalmanifestHashandfreezeTimestamp(deterministic replay against a frozen manifest); there is nooverrides/snapshot-substitution body. The flatScoreReplayResponsedefines the stored success contract, rather than a nestedscoreProof.nodes[]ledger. The/api/v1/score/{scanId}/*aliases are absent.
Current default behavior (PCD-403, 2026-10-06): a valid signed v2 manifest supplies snapshot addresses, seed and time; it does not supply attributable frozen CVSS, EPSS, reachability or provenance measurements. Default replay returns 422 with code scanner.score_replay.measurements_unavailable before creating scoring nodes, a bundle or a history entry. Repeated attempts produce the same refusal. Unknown scans retain 404; manifest schema/signature validation runs before scoring. Generic manifest knobs, digest bytes and current owner data cannot substitute for missing historical measurements.
Replay lock lifetime (PCD-406): within one ScoreReplayService instance, callers for the same scan ID (case-insensitive) share one semaphore through manifest lookup, signature validation, scoring and proof/history writes. Waiting callers reserve the same entry before waiting. Cancellation releases only that caller’s reservation; a caller releases the semaphore only after acquiring it. The final departing caller removes and disposes the entry, including after an unknown scan, measurement refusal, validation failure or write failure. Retained entries are bounded by distinct scan IDs with active or waiting replay callers; different scans proceed independently. This bound covers lock entries. Accepted replay history and canonical-binding metadata retain their existing process-local lifetime.
Lock lifetime verified against 34a66ad1c36412298f962eedb5ffef69a748825e on 2026-10-06. Re-verify with ScoreReplayLockLifecycleTests, ScoreReplayUnavailableInputsTests and ScoreReplayEndpointsTests through tools/scripts/test-targeted-xunit.ps1; acceptance evidence (pruned evidence docs/implplan/_evidence/20261006-scanner-replay-lock-lifecycle/PCD-406.md at ac2b8d47e7).
Request Body (ScoreReplayRequest, all optional):
{
"manifestHash": "sha256:manifest123...",
"freezeTimestamp": "2025-12-17T13:00:00Z"
}
Response (422 Unprocessable Entity, ProblemDetails, 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."
}
Related score endpoints (same ScoreReplayEndpoints.cs group, under /scans/{scanId}/score):
GET /api/v1/scans/{scanId}/score/bundle?rootHash=...(ScansRead) — proof bundle metadata + manifest DSSE validityPOST /api/v1/scans/{scanId}/score/verify(ScansWrite) — verify a bundle against an expected root hashGET /api/v1/scans/{scanId}/score/history(ScansRead) — this process’s accepted replay history; a refused replay adds no entry
Verification reads only the scan’s stored bundle selected by the expected root hash; the request has no bundle location member, and a bundleUri in the body is not read. Stored bundle retrieval and cryptographic verification retain their existing contracts. Signature/hash validity establishes artifact integrity; it does not establish that an old factor vector represented real measurements. Across service restart, verification requires the expected canonical hash and canonical payload together. No default production numeric formula is accepted for the address-only manifest contract. The withdrawn v2.factorized algorithm converted digest bytes into metric-labelled factors; it must not produce new proofs.
Errors:
400 Bad Request— Missing/invalid scan id404 Not Found— Scan ID not found. A scan of another tenant returns the same404with no body, before any manifest is read or any bundle or history entry is written.422 Unprocessable Entity— Required frozen measurements unavailable (stable code above), or manifest schema/signature validation failed
Changing a vulnerability snapshot address alone cannot rescore historical measurements. Re-verify with DeterministicScoringServiceTests, ScoreReplayUnavailableInputsTests and ScoreReplayEndpointsTests through tools/scripts/test-targeted-xunit.ps1. Verified against bf5d3c5b5f024db40f200593bb052746def175f1 on 2026-10-06; acceptance evidence (pruned evidence docs/implplan/_evidence/20261006-scanner-jobengine-carry/PCD-403.md at d887e8d5e2).
4. Upload Call-Graph
POST /api/v1/scans/{scanId}/callgraphs
Source: Endpoints/CallGraphEndpoints.cs → scanner.scans.callgraphs.submit. Scope policy: CallGraphIngest (requires scope scanner.callgraph.ingest). A Content-Digest request header is required (used as the idempotency key).
Description: Uploads a call-graph extracted by language-specific workers (dotnet, java, node, python, go, rust, binary, ruby, php).
Correction (2026-05-29): The schema must be exactly
stella.callgraph.v1, and a top-levelscanKeyfield is required in addition tonodes/edges. The response is aCallGraphAcceptedResponseDto(callgraphId,nodeCount,edgeCount,digest) — there is noscanId,entrypointsCount,status, or_linksfield on the response. A duplicateContent-Digestreturns409 Conflict(not a silent re-return of the existing graph).
Request Body (application/json, 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" }
]
}
Headers:
Content-Digest:sha256=<hash>(required — idempotency key)
Response (202 Accepted, CallGraphAcceptedResponseDto):
{
"callgraphId": "cg-001",
"nodeCount": 1234,
"edgeCount": 5678,
"digest": "sha256:cg123..."
}
A Location: /api/scans/{scanId}/callgraphs/{callgraphId} header is set.
Errors:
400 Bad Request— MissingContent-Digest, invalid identifier, or invalid call-graph schema404 Not Found— Scan ID not found409 Conflict— Duplicate submission (sameContent-Digest)413 Payload Too Large— Call-graph exceeds the configured size limit
5. Compute Reachability
POST /api/v1/scans/{scanId}/compute-reachability
Source: Endpoints/ReachabilityEndpoints.cs → scanner.scans.compute-reachability. Scope policy: ScansWrite.
Correction (2026-05-29): The implemented route is
/scans/{scanId}/compute-reachability, not/scans/{scanId}/reachability/compute. The response isComputeReachabilityResponseDto(jobId,status,estimatedDuration) with noscanIdor_linksblock. The default Scanner wiring registers aNullReachabilityComputeService; when no reachability runtime is configured the endpoint returns 501 Not Implemented (scanner.reachability.unavailable). There is no documented Scanner/jobs/{jobId}polling route — poll the findings endpoint (Endpoint 6) instead.
Request Body (ComputeReachabilityRequestDto, optional): forceRecompute, entrypoints, targets.
Response (202 Accepted, ComputeReachabilityResponseDto):
{
"jobId": "reachability-job-001",
"status": "queued",
"estimatedDuration": "30s"
}
Errors:
400 Bad Request— Invalid scan identifier404 Not Found— Scan ID not found409 Conflict— Computation already in progress501 Not Implemented— No reachability runtime configured for this deployment
6. Get Reachability Findings
GET /api/v1/scans/{scanId}/reachability/findings
Source: Endpoints/ReachabilityEndpoints.cs → scanner.scans.reachability.findings. Scope policy: ScansRead.
Correction (2026-05-29): The query parameters are
cveandstatus(notcveId). The response is aReachabilityFindingListDtowith a flatitems[]ofReachabilityFindingDtoand atotalcount — there is noscanId,computedAt, per-findingpath/evidence/_links, nor asummaryaggregate object. Use Endpoint 7 for path witnesses. A sibling routeGET /scans/{scanId}/reachability/components(paramspurl,status) returnsComponentReachabilityListDto, andGET /scans/{scanId}/reachability/traces/export(paramsformat,includeRuntimeEvidence,minReachabilityScore,runtimeConfirmedOnly) emits a deterministic callable-path / sensor-gap trace export.
Query Parameters:
cve(optional): Filter by CVE IDstatus(optional): 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
}
Errors:
400 Bad Request— Invalid scan identifier404 Not Found— Scan ID not found501 Not Implemented— Reachability query backend not configured
7. Explain Reachability
GET /api/v1/scans/{scanId}/reachability/explain
Source: Endpoints/ReachabilityEndpoints.cs → scanner.scans.reachability.explain. Scope policy: ScansRead.
Correction (2026-05-29): Response is
ReachabilityExplanationDto. The path witness is apathWitnessstring list (e.g."A -> B -> C"), the reasons are awhy[]list of{code, description, impact}, and supporting data is grouped underevidence(staticAnalysis,runtimeEvidence,policyEvaluation), plus an optionalspineId. There is noexplanation.shortestPath[],confidenceFactors,alternativePaths, or_linksobject. Missingcveorpurlreturns 400, not 404.
Query Parameters:
cve(required): CVE IDpurl(required): 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": [
"MyApp.Controllers.OrdersController::Get -> MyApp.Services.OrderService::Process -> Lodash.merge"
],
"why": [
{ "code": "static_path", "description": "Static call path exists from HTTP entrypoint", "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"
}
Errors:
400 Bad Request— Invalid scan id, or missingcve/purl404 Not Found— Scan or explanation not found501 Not Implemented— Reachability runtime not configured
8. Fetch Proof Bundle Metadata
GET /api/v1/scans/{scanId}/proofs/{rootHash}
Source: Endpoints/ManifestEndpoints.cs → scanner.scans.proofs.get. Scope policy: ScansRead.
Correction (2026-05-29): This endpoint returns a JSON
ProofBundleResponsedescribing the proof bundle (hashes, signature metadata, DSSE validity) — it does not stream anapplication/ziparchive, and there are noContent-Disposition/X-Proof-Root-Hash/X-Manifest-Hashheaders. A sibling routeGET /api/v1/scans/{scanId}/proofs(scanner.scans.proofs.list) lists all proof bundles for a scan. The score-replay flow’s bundle pointer is exposed viaGET /api/v1/scans/{scanId}/score/bundle?rootHash=...(see Endpoint 3). No CLIstella proof verify --bundle proof.zippath was confirmed against the WebService surface.
Path Parameters:
rootHash: Proof root hash (e.g.,sha256:proof123...)
Response (200 OK, application/json, ProofBundleResponse):
{
"scanId": "550e8400-e29b-41d4-a716-446655440000",
"rootHash": "sha256:proof123...",
"bundleType": "score-proof",
"bundleHash": "sha256:bundle...",
"ledgerHash": "sha256:ledger...",
"manifestHash": "sha256:manifest...",
"sbomHash": "sha256:sbom...",
"vexHash": "sha256:vex...",
"signatureKeyId": "ecdsa-p256-key-001",
"signatureAlgorithm": "ES256",
"createdAt": "2025-12-17T12:00:00Z",
"expiresAt": null,
"signatureValid": true,
"verificationError": null,
"contentDigest": "sha-256=:<base64>:"
}
Errors:
404 Not Found— Scan, invalid id format, or proof root hash not found. A scan of another tenant returns the same404with no body.
9. List Unknowns
GET /api/v1/unknowns
Source: Endpoints/UnknownsEndpoints.cs → scanner.unknowns.list. Scope policy: ScansRead.
Correction (2026-05-29): Unknowns are mounted at
/api/v1/unknowns(NOT/api/v1/scanner/unknowns). Query parameters areartifactDigest,vulnId,band,sortBy(score|created|updated),sortOrder(asc|desc),limit(default 50, clamped 1–500), andoffset. The response isUnknownsListResponse(items[],totalCount,limit,offset); list items exposeid,artifactDigest,vulnerabilityId,packagePurl,score,band,createdAtUtc,updatedAtUtc. Thepopularity/potentialExploit/uncertainty/evidencefields, thepagination.nextlink, and the per-item_links.escalatelink do not exist.
Query Parameters:
band(optional): Filter byHOT,WARM,COLDartifactDigest,vulnId(optional): filterssortBy,sortOrder,limit,offset(optional)
Response (200 OK, UnknownsListResponse):
{
"items": [
{
"id": "unk-3f1c...",
"artifactDigest": "sha256:abc123...",
"vulnerabilityId": "CVE-2024-1234",
"packagePurl": "pkg:npm/lodash@4.17.20",
"score": 0.72,
"band": "HOT",
"createdAtUtc": "2025-12-15T10:00:00Z",
"updatedAtUtc": "2025-12-16T10:00:00Z"
}
],
"totalCount": 156,
"limit": 50,
"offset": 0
}
Other implemented unknowns routes (same UnknownsEndpoints.cs group, all ScansRead):
GET /api/v1/unknowns/stats→UnknownsStatsResponse(total,hot,warm,cold)GET /api/v1/unknowns/bands→ band distribution mapGET /api/v1/unknowns/{id}→UnknownDetailResponseGET /api/v1/unknowns/{id}/evidence→UnknownEvidenceResponse(id,proofRef,lastUpdatedAtUtc)GET /api/v1/unknowns/{id}/history→UnknownHistoryResponse
Errors:
400 Bad Request— Invalid band value or tenant context
10. Escalate Unknown to Rescan — NOT IMPLEMENTED
Removed / not implemented (2026-05-29): No
POST /unknowns/{id}/escalateendpoint exists inEndpoints/UnknownsEndpoints.cs(which is read-only: only theGETroutes listed in Endpoint 9). There is likewise noresolveorsummaryroute on the unknowns group. The escalate/resolve/summary endpoints were never backed by a controller and have been removed. If escalation is required, file a sprint to add a write route + audit action before re-documenting it.
Data Models
ScanManifest
See src/Scanner/__Libraries/StellaOps.Scanner.Core/ScanManifest.cs for the internal manifest model. Note that the wire shape returned by GET /scans/{scanId}/manifest (ScanManifestResponse, Contracts/ManifestContracts.cs) projects only sbomHash, vexSnapshotHash, vulnerabilitiesSnapshotHash, policyHash (plus versions/timestamps) — see Endpoint 2.
ProofNode
Conceptual model for the score-proof ledger; ProofNode arrays are not returned inline in the stored success contract; default replay now refuses unavailable measurements (see Endpoint 3).
interface ProofNode {
id: string;
kind: "Input" | "Transform" | "Delta" | "Score";
ruleId: string;
parentIds: string[];
evidenceRefs: string[];
delta: number;
total: number;
actor: string;
tsUtc: string; // ISO 8601
seed: string; // base64
nodeHash: string; // sha256:...
}
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"
}
Error Responses
All errors follow RFC 7807 (Problem Details):
{
"type": "https://stella-ops.org/errors/scan-not-found",
"title": "Scan Not Found",
"status": 404,
"detail": "Scan ID '550e8400-e29b-41d4-a716-446655440000' does not exist.",
"instance": "/api/v1/scans/550e8400-e29b-41d4-a716-446655440000",
"traceId": "trace-001"
}
Error Types
Note (2026-05-29): The codebase builds problem responses from localization keys (e.g.
scanner.scan.not_found,scanner.callgraph.invalid) viaProblemResultFactory, not from a fixedtypeslug registry. The slugs below are illustrative. Theinvalid-manifest,duplicate-scan,snapshot-not-found, andescalation-conflictrows describe the removed manifest-creation / escalate contracts and do not correspond 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 computation already running |
reachability-unavailable | 501 | Reachability runtime not configured |
Rate Limiting
Correction (2026-05-29): Only
GET /scans/{scanId}/manifestis rate-limited in code (RequireRateLimiting(ManifestPolicy), wired viaAddScannerRateLimiting()). The per-tenant hourly quotas previously listed forPOST /scans, replay, callgraphs, and reachability are not enforced by the current WebService and were aspirational. StandardX-RateLimit-*headers and429responses apply to the manifest endpoint per the configured policy.
Webhooks (Future)
Webhook registration (scan.completed, reachability.computed, etc.) is not implemented. The WebhookEndpoints.cs group exists for inbound PR-annotation webhook handling, not for event-subscription registration. No POST /webhooks subscription route was confirmed.
OpenAPI Specification
File: src/Api/StellaOps.Api.OpenApi/scanner/openapi.yaml (present in repo).
Source Map
| Endpoint | Backing class | Endpoint name |
|---|---|---|
POST /api/v1/scans | Endpoints/ScanEndpoints.cs | scanner.scans.submit |
GET /api/v1/scans/{scanId} | Endpoints/ScanEndpoints.cs | scanner.scans.status |
GET /api/v1/scans/{scanId}/manifest | Endpoints/ManifestEndpoints.cs | scanner.scans.manifest |
GET /api/v1/scans/{scanId}/proofs | Endpoints/ManifestEndpoints.cs | scanner.scans.proofs.list |
GET /api/v1/scans/{scanId}/proofs/{rootHash} | Endpoints/ManifestEndpoints.cs | scanner.scans.proofs.get |
POST /api/v1/scans/{scanId}/score/replay | Endpoints/ScoreReplayEndpoints.cs | scanner.scans.score.replay |
GET /api/v1/scans/{scanId}/score/bundle | Endpoints/ScoreReplayEndpoints.cs | scanner.scans.score.bundle |
POST /api/v1/scans/{scanId}/score/verify | Endpoints/ScoreReplayEndpoints.cs | scanner.scans.score.verify |
GET /api/v1/scans/{scanId}/score/history | Endpoints/ScoreReplayEndpoints.cs | scanner.scans.score.history |
POST /api/v1/scans/{scanId}/callgraphs | Endpoints/CallGraphEndpoints.cs | scanner.scans.callgraphs.submit |
POST /api/v1/scans/{scanId}/compute-reachability | Endpoints/ReachabilityEndpoints.cs | scanner.scans.compute-reachability |
GET /api/v1/scans/{scanId}/reachability/findings | Endpoints/ReachabilityEndpoints.cs | scanner.scans.reachability.findings |
GET /api/v1/scans/{scanId}/reachability/components | Endpoints/ReachabilityEndpoints.cs | scanner.scans.reachability.components |
GET /api/v1/scans/{scanId}/reachability/explain | Endpoints/ReachabilityEndpoints.cs | scanner.scans.reachability.explain |
GET /api/v1/scans/{scanId}/reachability/traces/export | Endpoints/ReachabilityEndpoints.cs | scanner.scans.reachability.traces.export |
GET /api/v1/scans/{scanId}/spines | Endpoints/ProofSpineEndpoints.cs | scanner.spines.list-by-scan |
GET /api/v1/spines/{spineId} | Endpoints/ProofSpineEndpoints.cs | scanner.spines.get |
GET /api/v1/unknowns (+ /stats,/bands,/{id},/{id}/evidence,/{id}/history) | Endpoints/UnknownsEndpoints.cs | scanner.unknowns.* |
POST /api/v1/triage/proof-bundle | Endpoints/Triage/ProofBundleEndpoints.cs | scanner.triage.proof-bundle |
Base path group is app.MapGroup(resolvedOptions.Api.BasePath) in Program.cs (Options/ScannerWebServiceOptions.cs → Api.BasePath default /api/v1).
References
SPRINT_3500_0002_0001_score_proofs_foundations.md— implementation sprintSPRINT_3500_0002_0003_proof_replay_api.md— API implementation sprintSPRINT_3500_0003_0003_graph_attestations_rekor.md— reachability API sprint- Architecture overview — API contracts section
- Scanner schema specification — database schema
- Score Proofs & Reachability API Reference — companion reference (same surface, with the Attestor proof-chain context)
Last Updated: 2026-05-29 (doc↔code reconciliation) API Version: 2.0 Next Review: Sprint 3500.0004.0001 (CLI integration)
