Proof Chain API Reference
Version: 1.0.0
Status: Forward-looking spec — the spine/anchor/verify surface below is not yet implemented (501 stubs). See the banner below.
OpenAPI Spec:proofs-openapi.yaml(present; describes the forward-looking contract, which is currently 501-stubbed in the service).
Reconciliation banner (2026-05-29). Verified against
src/Attestor/StellaOps.Attestor/StellaOps.Attestor.WebService/:
- The endpoints in this document (
/proofs/{entry}/spine,/proofs/{entry}/receipt,/proofs/{entry}/vex,/anchors*,/verify) are backed byProofsController,AnchorsController, andVerifyController. Every handler returns501 Not Implemented, gated behind feature flags (ProofsEnabled/AnchorsEnabled/VerifyEnabled, all defaultfalse); even when a flag is enabled the body still returns 501. Treat this whole document as a forward-looking contract, not live API.- The controllers are mounted root-relative (
[Route("proofs")],[Route("anchors")],[Route("verify")]) — there is no/api/v1prefix on these routes.- Authorization uses policies
attestor:read/attestor:write/attestor:verify, which require the JWT scopesattestor.read/attestor.write/attestor.verify(NOTproofs.read/proofs.write/proofs.verify/anchors.manage, which do not exist).- A separate, implemented proof-chain query controller (
ProofChainController) exists at/api/v1/proofs/{subjectDigest}keyed by artifact subject digest — see Implemented proof-chain queries.
The Proof Chain API creates and verifies cryptographic proof bundles that link SBOM entries to vulnerability assessments through attestable DSSE envelopes. This document describes the forward-looking spine/anchor/verify contract; for the proof-chain queries you can call today, see Implemented proof-chain queries.
Overview
The proof chain creates an auditable, cryptographically-verifiable trail from vulnerability evidence through policy reasoning to VEX verdicts. Each component is signed with DSSE envelopes and aggregated into a merkle-rooted proof spine.
Proof Chain Components
| Component | Predicate Type | Purpose |
|---|---|---|
| Evidence | evidence.stella/v1 | Raw findings from scanners/feeds |
| Reasoning | reasoning.stella/v1 | Policy evaluation trace |
| VEX Verdict | cdx-vex.stella/v1 | Final VEX status determination |
| Proof Spine | proofspine.stella/v1 | Merkle aggregation of all components |
| Verdict Receipt | verdict.stella/v1 | Human-readable verification receipt |
Content-Addressed IDs
All proof chain components use content-addressed identifiers:
Format: sha256:<64-hex-chars>
Example: sha256:e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6...
IDs are computed by:
- Canonicalizing the JSON payload (RFC 8785/JCS)
- Computing SHA-256 hash
- Prefixing with
sha256:
Authentication
All endpoints require authentication via:
- Bearer Token: Authority-issued OpToken with appropriate scopes
- mTLS: Mutual TLS with client certificate (service-to-service)
Required scopes (corrected 2026-05-29 — the previously listed proofs.read / proofs.write / proofs.verify / anchors.manage scopes do not exist):
attestor.read— read proof spines, receipts, VEX, anchors (policyattestor:read)attestor.write— create proof spines, manage trust anchors (policyattestor:write)attestor.verify— perform verification (policyattestor:verify;attestor.writealso satisfies it)
Endpoints
All routes in this section are root-relative (no
/api/v1prefix) and currently return 501 Not Implemented (ProofsController/AnchorsController/VerifyController). The request/response shapes below are the intended contract.
Proofs
POST /proofs/{entry}/spine — 501 (not implemented)
ProofsController.CreateSpineAsync, scope attestor:write. Create a proof spine for an SBOM entry.
Parameters:
entry(path, required): SBOMEntryID in formatsha256:<hash>:pkg:<purl>
Request Body:
{
"evidenceIds": ["sha256:e7f8a9b0..."],
"reasoningId": "sha256:f0e1d2c3...",
"vexVerdictId": "sha256:d4c5b6a7...",
"policyVersion": "v1.2.3"
}
Response (201 Created):
{
"proofBundleId": "sha256:1a2b3c4d...",
"receiptUrl": "/proofs/sha256:abc:pkg:npm/lodash@4.17.21/receipt"
}
Errors:
400 Bad Request: Invalid SBOM entry ID format404 Not Found: Evidence, reasoning, or VEX verdict not found422 Unprocessable Entity: Validation error
GET /proofs/{entry}/spine — 501 (not implemented)
ProofsController.GetSpineAsync, scope attestor:read. Get the proof spine for an SBOM entry.
Parameters:
entry(path, required): SBOMEntryID
Response (200 OK):
{
"sbomEntryId": "sha256:abc123:pkg:npm/lodash@4.17.21",
"proofBundleId": "sha256:1a2b3c4d...",
"evidenceIds": ["sha256:e7f8a9b0..."],
"reasoningId": "sha256:f0e1d2c3...",
"vexVerdictId": "sha256:d4c5b6a7...",
"policyVersion": "v1.2.3",
"createdAt": "2025-12-17T10:00:00Z"
}
GET /proofs/{entry}/receipt — 501 (not implemented)
ProofsController.GetReceiptAsync, scope attestor:read. Get the verification receipt for an SBOM entry’s proof spine.
Response (200 OK):
{
"graphRevisionId": "grv_sha256:9f8e7d6c...",
"findingKey": {
"sbomEntryId": "sha256:abc123:pkg:npm/lodash@4.17.21",
"vulnerabilityId": "CVE-2025-1234"
},
"rule": {
"id": "critical-vuln-block",
"version": "v1.0.0"
},
"decision": {
"verdict": "pass",
"severity": "none",
"reasoning": "Not affected - vulnerable code not present"
},
"createdAt": "2025-12-17T10:00:00Z",
"verified": true
}
GET /proofs/{entry}/vex — 501 (not implemented)
ProofsController.GetVexAsync, scope attestor:read. Get the VEX attestation for an SBOM entry.
Response (200 OK):
{
"sbomEntryId": "sha256:abc123:pkg:npm/lodash@4.17.21",
"vulnerabilityId": "CVE-2025-1234",
"status": "not_affected",
"justification": "vulnerable_code_not_present",
"policyVersion": "v1.2.3",
"reasoningId": "sha256:f0e1d2c3...",
"vexVerdictId": "sha256:d4c5b6a7..."
}
Trust Anchors
All anchor routes are served by
AnchorsController(root-relative/anchors) and return 501 Not Implemented. Full implemented route set:GET /anchors(attestor:read),GET /anchors/{anchorId}(attestor:read),POST /anchors(attestor:write),PATCH /anchors/{anchorId:guid}(attestor:write),POST /anchors/{anchorId:guid}/revoke-key(attestor:write),DELETE /anchors/{anchorId:guid}(attestor:write).
GET /anchors — 501 (not implemented)
List all configured trust anchors.
Response (200 OK):
{
"anchors": [
{
"id": "anchor-001",
"keyId": "sha256:abc123...",
"algorithm": "ECDSA-P256",
"status": "active",
"createdAt": "2025-01-01T00:00:00Z"
}
]
}
POST /anchors — 501 (not implemented)
Create a new trust anchor. (Implemented request DTO is CreateTrustAnchorRequest, which is pattern-based — purlPattern — rather than the keyId/algorithm/publicKey body shown below.)
Request Body:
{
"keyId": "sha256:abc123...",
"algorithm": "ECDSA-P256",
"publicKey": "-----BEGIN PUBLIC KEY-----\n..."
}
Response (201 Created):
{
"id": "anchor-002",
"keyId": "sha256:abc123...",
"algorithm": "ECDSA-P256",
"status": "active",
"createdAt": "2025-12-17T10:00:00Z"
}
DELETE /anchors/{anchorId} — 501 (not implemented)
Delete (deactivate) a trust anchor. Route correction: the parameter is constrained to a GUID (DELETE /anchors/{anchorId:guid}). To revoke a key within an anchor (not deactivate the whole anchor), the implemented route is POST /anchors/{anchorId:guid}/revoke-key with a RevokeKeyRequest body, not this DELETE.
Response (intended): 204 No Content
Verification
Served by
VerifyController(root-relative/verify), all 501 Not Implemented. Implemented routes:POST /verify/{proofBundleId}(attestor:verify),GET /verify/envelope/{envelopeHash}(attestor:read),GET /verify/rekor/{envelopeHash}(attestor:read).
POST /verify/{proofBundleId} — 501 (not implemented)
Perform full verification of a proof bundle.
Route correction (2026-05-29): The bundle id is a path parameter (
POST /verify/{proofBundleId}); there is noPOST /verifycollection route taking the id in the body. TheproofBundleIdfield in the body example below is therefore redundant with the path. There is noPOST /verify/batchendpoint (see below).
Request Body (VerifyProofRequest, optional):
{
"proofBundleId": "sha256:1a2b3c4d...",
"checkRekor": true,
"anchorIds": ["anchor-001"]
}
Response (200 OK):
{
"proofBundleId": "sha256:1a2b3c4d...",
"verified": true,
"checks": {
"signatureValid": true,
"idRecomputed": true,
"merklePathValid": true,
"rekorInclusionValid": true
},
"errors": [],
"verifiedAt": "2025-12-17T10:00:00Z"
}
Verification Steps:
- Signature Verification: Verify DSSE envelope signatures against trust anchors
- ID Recomputation: Recompute content-addressed IDs and compare
- Merkle Path Verification: Verify proof bundle merkle tree construction
- Rekor Inclusion: Verify transparency log inclusion proof (if enabled)
POST /verify/batch — DOES NOT EXIST
Removed (2026-05-29):
VerifyControllerexposes no batch route. The implemented sibling verify routes areGET /verify/envelope/{envelopeHash}andGET /verify/rekor/{envelopeHash}(both currently 501). If batch verification is required, file a sprint before re-documenting it. Note the Attestor does have a separate Rekor bulk-verify minimal endpoint atPOST /api/v1/rekor/verify:bulk(different surface from this proof-chain spec).
Implemented proof-chain queries (LIVE)
While the spine/anchor/verify surface above is stubbed, ProofChainController (route /api/v1/proofs, scope attestor:read) is implemented and keyed by artifact subject digest:
| Method | Route | Scope | Description |
|---|---|---|---|
| GET | /api/v1/proofs/{subjectDigest} | attestor:read | List proofs for an artifact (ProofListResponse) |
| GET | /api/v1/proofs/{subjectDigest}/chain?maxDepth= | attestor:read | Evidence-chain graph (ProofChainResponse; depth 1–10, default 5) |
| GET | /api/v1/proofs/id/{proofId} | attestor:read | Proof detail (ProofDetail) |
| GET | /api/v1/proofs/id/{proofId}/verify | attestor:verify | Verify a proof (ProofVerificationResult) |
This is the controller to use for “Show Me The Proof” workflows today; the {entry}/spine creation flow above remains future work.
Error Handling
All errors follow RFC 7807 Problem Details format:
{
"title": "Validation Error",
"detail": "Evidence ID sha256:abc... not found",
"status": 422,
"errors": {
"evidenceIds[0]": ["Evidence not found"]
}
}
Common Error Codes
| Status | Meaning |
|---|---|
| 400 | Invalid request format or parameters |
| 401 | Authentication required |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 409 | Conflict (e.g., anchor already exists) |
| 422 | Validation error |
| 500 | Internal server error |
Offline Verification
For air-gapped environments, verification can be performed without Rekor:
{
"proofBundleId": "sha256:1a2b3c4d...",
"checkRekor": false
}
This skips Rekor inclusion proof verification but still performs:
- DSSE signature verification
- Content-addressed ID recomputation
- Merkle path verification
Related Documentation
- Proof Chain Predicates — DSSE predicate type specifications
- Content-Addressed IDs — ID generation rules
- Attestor Architecture — full Attestor module documentation
- CanonJson API Reference — RFC 8785 canonicalization and content-addressed hashing
- Score Proofs & Reachability API Reference — the implemented proof-chain query surface in context
