# Governed Attestation Contract (Node 0.30.1)

URL: https://docs.nexart.io/docs/governed-execution/attestation-contract

Exact governed /api/attest request and response, receipt and verification-envelope signature procedures, stateless verification, persistence and visibility for Node 0.30.1.

Every field on this page comes from Node production source and executable tests for the released Node 0.30.1 source, with `@nexart/governed-execution@0.4.2` as the package contract. Placeholders in angle brackets mark dynamic values only. No fields from AI Execution CERs are included. See the source ledger.

## Two different request wrappers

| Route | Auth | Body |
| --- | --- | --- |
| POST /api/attest | Authorization: Bearer <api_key> | The complete sealed CER itself, top-level |
| POST /v1/cer/verify | None | { "bundle": <sealed CER> } |

MUST NOTWrap the CER in `{ bundle }` for `/api/attest`, or send it unwrapped to `/v1/cer/verify`.

## Attestation request

| Property | Contract |
| --- | --- |
| Method and path | POST /api/attest |
| Content type | application/json |
| Family | Exact bundleType: "cer.governed.execution.v1" |
| CER version | "0.1" or "0.2". Not the Node version, SDK version or AI protocol version. |
| Logical execution identity | evidence.bundle.bundleId, not threadId |
| Signer selection | The Node selects the key. Neither kid nor nodeId is sent. |
| X-NexArt-Visibility | Optional; exactly public or private. hidden is not an accepted header value. Omitted: existing proof's visibility, otherwise hidden. |
| X-NexArt-Readable-Fields | Rejected for governed records with READABLE_FIELDS_UNSUPPORTED. Consequential-family feature only. |

```
POST /api/attest
Authorization: Bearer <TEMPORARY_API_KEY>
Content-Type: application/json
```

Redacted v0.1 request body (derived from tests/fixtures/governed-static-mutual-cycle.json)

```
{
  "bundleType": "cer.governed.execution.v1",
  "createdAt": "<RFC3339_CREATED_AT>",
  "version": "0.1",
  "profile": {
    "id": "cage-governance-v1",
    "version": "1",
    "contract": "urn:cage:governance:v1:attestation-bundle",
    "source": { "system": "CAGE", "profile": "provider_02" }
  },
  "evidence": {
    "bundle": {
      "bundleId": "<UUID_BUNDLE>",
      "threadId": "<CORRELATION_ID>",
      "startedAt": "<RFC3339_START>",
      "completedAt": "<RFC3339_COMPLETION>",
      "terminalPath": "happy_path",
      "steps": [
        {
          "stepId": "<UUID_STEP_1>",
          "nodeName": "execution_analyst",
          "parentStepIds": [],
          "timestampUtc": "<RFC3339_STEP_1>",
          "durationMs": 1,
          "signals": {},
          "metadata": {},
          "stateHash": "<64_LOWERCASE_HEX>"
        },
        {
          "stepId": "<UUID_STEP_2>",
          "nodeName": "evaluator",
          "parentStepIds": ["<UUID_STEP_1>"],
          "timestampUtc": "<RFC3339_STEP_2>",
          "durationMs": 1,
          "signals": {},
          "metadata": {},
          "stateHash": "<64_LOWERCASE_HEX>"
        }
      ]
    },
    "topology": {
      "nodes": ["evaluator", "execution_analyst"],
      "parentEdges": {
        "evaluator": ["execution_analyst"],
        "execution_analyst": ["evaluator"]
      },
      "terminalNode": "evaluator"
    }
  },
  "protectedSet": {
    "stabilitySchemeId": "jcs-v1",
    "protectedSetId": "nexart.governed.execution.v1.protected-set.v1",
    "protectedFields": ["bundleType", "createdAt", "version", "profile", "evidence"]
  },
  "certificateHash": "sha256:<64_LOWERCASE_HEX>"
}
```

Processing order: JSON parser, structure guard, API key, header checks, quota, family recognition, owner-scoped replay lookup, SDK `verifyGovernedExecution` (rejected unless `ok === true`), mutation and replay handling, transactional signing and persistence. The Node does not replace the submitted `certificateHash`.

### Source-default limits

- HTTP JSON body: 8 MiB default, configurable via `MAX_JSON_BODY` up to 32 MiB.
- Structure guard: depth 64, 100,000 total JSON nodes, 10,000 items per container (413 `REQUEST_STRUCTURE_TOO_COMPLEX`).
- SDK evidence: depth 32, 4,096 array items, 4,096 object keys, 1 MiB canonical evidence.
- CAGE SDK: 256 steps, 1,024 topology nodes, 4,096 topology edges.
- Monthly account quota applies. `/api/attest` does not mount the authenticated per-minute limiter.

These are source defaults and caps, not observations of production configuration.

## Successful response (HTTP 200)

Redacted v0.1 response, topology omitted, TSA disabled

```
{
  "ok": true,
  "bundleType": "cer.governed.execution.v1",
  "certificateHash": "sha256:<CERTIFICATE_HEX>",
  "attestationId": "<UUID_ATTESTATION>",
  "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
  "protocolVersion": "0.1",
  "issuedAt": "<RFC3339_ATTESTED_AT>",
  "kid": "<KID>",
  "attestorKeyId": "<KID>",
  "attestation": {
    "attestedAt": "<RFC3339_ATTESTED_AT>",
    "attestationId": "<UUID_ATTESTATION>",
    "bundleType": "cer.governed.execution.v1",
    "certificateHash": "sha256:<CERTIFICATE_HEX>",
    "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
    "protocolVersion": "0.1",
    "requestId": "<UUID_ATTESTATION>",
    "verified": true,
    "checks": ["governed_execution"]
  },
  "receipt": {
    "receiptVersion": "attestation.receipt.v1",
    "attestationId": "<UUID_ATTESTATION>",
    "certificateHash": "sha256:<CERTIFICATE_HEX>",
    "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
    "protocolVersion": "0.1",
    "attestedAt": "<RFC3339_ATTESTED_AT>",
    "timestampType": "node-issued",
    "nodeId": "<NODE_ID>",
    "attestorKeyId": "<KID>"
  },
  "signature": "<BASE64URL_RECEIPT_SIGNATURE>",
  "verificationEnvelope": {
    "algorithm": "Ed25519",
    "attestation": {
      "attestationId": "<UUID_ATTESTATION>",
      "attestedAt": "<RFC3339_ATTESTED_AT>",
      "kid": "<KID>",
      "nodeRuntimeHash": "sha256:<RUNTIME_HEX>",
      "protocolVersion": "0.1"
    },
    "canonicalization": "jcs",
    "envelopeType": "nexart.verification.envelope.v2",
    "kid": "<KID>",
    "scope": "whitelist",
    "signedFields": {
      "attestation": ["attestationId", "attestedAt", "kid", "nodeRuntimeHash", "protocolVersion"],
      "bundle": ["bundleType", "version", "createdAt", "profile", "evidence", "protectedSet"]
    },
    "specVersion": "v0.16.1"
  },
  "verificationEnvelopeSignature": "<BASE64URL_ENVELOPE_SIGNATURE>",
  "visibility": "hidden",
  "governedVerification": {
    "certificateIntegrity": "valid",
    "cageSchemaValidity": "valid",
    "causalGraphValidity": "valid",
    "topologyValidation": "not-supplied",
    "resourceSafety": "valid",
    "stateHashVerification": "not-performed",
    "producerAuthentication": "not-performed",
    "completeness": "not-claimed",
    "warnings": []
  },
  "verificationUrl": "https://verify.nexart.io/c/sha256%3A<CERTIFICATE_HEX>"
}
```

| Field | Meaning |
| --- | --- |
| ok | true |
| certificateHash | The caller's sealed hash, unchanged |
| attestationId | Node-generated UUID; equals attestation.requestId on the fresh path |
| nodeRuntimeHash | sha256:<hex> of the canonical Node runtime descriptor |
| protocolVersion | Mirrors the CER version: "0.1" or "0.2" |
| issuedAt | Node attestedAt |
| kid, attestorKeyId | The selected signing key ID |
| attestation.checks | Array ["governed_execution"]. Not the stateless checks object. |
| receipt, signature | Receipt and its base64url Ed25519 signature (procedure) |
| verificationEnvelope, verificationEnvelopeSignature | v2 descriptor and projection signature (procedure) |
| visibility | Actual local visibility: hidden, public or private |
| governedVerification | Nine-key report (fields) |
| verificationUrl | Formatted string. Not a read-after-write publication check. |
| timestamp | Optional; present only when a TSA is configured |

Absent from this response: the CER, top-level `status`, top-level `nodeId` (it is inside the receipt), `receiptSignature`, `signatureEncoding`, top-level `signedFields` (inside the envelope), `persisted`, `published`, `publicRecordId` and `readableFields`. For the cyclic-topology request above, `topologyValidation` is `"valid"`.

With a configured, successful RFC 3161 TSA, the receipt adds `timestampStatus: "ok"` and `timestampProvider`, `timestampType` becomes `"rfc3161"`, and the response adds:

```
{
  "timestamp": {
    "type": "rfc3161",
    "status": "ok",
    "provider": "<TSA_PROVIDER>",
    "token": "<BASE64_DER_TOKEN>"
  }
}
```

Token bytes are outside the certificate, receipt and envelope signed perimeters. No production TSA configuration is implied.

### Replay and mutation

- An exact owner-authorized replay returns the original response with `X-Idempotent-Replay: true`. No new identity is minted.
- The same `bundleId` with a different valid hash returns 409 `EXECUTION_MUTATION_DETECTED`.

### Failure responses

| HTTP | Condition |
| --- | --- |
| 400 | Invalid JSON or shape, unsupported family, invalid visibility or readability headers |
| 400 | SDK rejection: { "error": "INVALID_BUNDLE", "reasonCode": "<SDK_CODE>", "details": ["<SDK_ERROR>"] } |
| 401 | Missing, invalid or inactive Bearer API key (UNAUTHORIZED) |
| 404 | Owner or privacy authorization failure, indistinguishable from a nonexistent record |
| 409 | Logical execution mutation, visibility conflict, conflicting replay |
| 413 | PAYLOAD_TOO_LARGE or REQUEST_STRUCTURE_TOO_COMPLEX |
| 429 | Monthly QUOTA_EXCEEDED; the failed-authentication limiter may also reject |
| 503 | Auth database unavailable under fail-closed metering; bounded lock or service-unavailable paths |
| 500 | Persistence, signing or internal failure. Do not assume success. |

Error shapes are branch-specific, not a uniform object.

## Receipt signature verification

The receipt contains, in construction order:

```
receiptVersion, attestationId, certificateHash, nodeRuntimeHash,
protocolVersion, attestedAt, timestampType, nodeId, attestorKeyId
(+ timestampStatus, timestampProvider when supplied)
```

Every field actually present in the receipt is signed. There is no signed-field subset.

```
bytes     = UTF8(canonicalize(receipt))     // @nexart/ai-execution toCanonicalJson
signature = base64url(Ed25519.sign(bytes))  // URL-safe, no padding
```

The canonicalizer sorts object keys, preserves array order, uses JSON string escaping and ECMAScript number serialization, emits no whitespace, rejects non-finite numbers and omits undefined members. Do not normalize Unicode, sort arrays or substitute ordinary JSON serialization.

- Obtain `receipt` and `signature`.
- Read `receipt.attestorKeyId`; cross-check with response `kid` and `verificationEnvelope.attestation.kid`.
- Resolve that exact `kid` in `keys[]` of `GET /.well-known/nexart-node.json`, not `keys[0]` or `activeKid`. Rotated or deprecated keys can verify older signatures.
- Build the Ed25519 public key from `publicKeyJwk`, `publicKeySpkiB64` or the raw 32-byte base64url `publicKey` (SPKI DER prefix `302a300506032b6570032100`).
- Apply key lifecycle policy: reject unknown or revoked keys, enforce explicit validity windows. A boot-time `notBefore` on the active key is not an authoritative lower bound for old records.
- Base64url-decode the signature and verify Ed25519 over the canonical receipt bytes.
- Cross-check `certificateHash`, `attestationId`, `nodeRuntimeHash` and `protocolVersion` against the CER and envelope.

verify-receipt.ts

```
import { createPublicKey, verify } from "node:crypto";
import { toCanonicalJson } from "@nexart/ai-execution"; // the canonicalizer the Node uses

const NODE = process.env.NEXART_NODE_ENDPOINT; // e.g. https://node.nexart.io
const { receipt, signature, kid } = attestResponse;

// 1-2. Key ID comes from the receipt; cross-check the response and envelope.
const receiptKid = receipt.attestorKeyId;
if (receiptKid !== kid || receiptKid !== attestResponse.verificationEnvelope.attestation.kid) {
  throw new Error("kid mismatch");
}

// 3. Resolve that exact kid. Never use keys[0] or activeKid by position.
const discovery = await (await fetch(`${NODE}/.well-known/nexart-node.json`)).json();
const key = discovery.keys.find((k) => k.kid === receiptKid);
if (!key || key.revoked || key.status === "revoked") throw new Error("unknown or revoked key");

// 4. Raw 32-byte base64url key -> SPKI DER with the Ed25519 prefix.
const spki = Buffer.concat([
  Buffer.from("302a300506032b6570032100", "hex"),
  Buffer.from(key.publicKey, "base64url"),
]);
const publicKey = createPublicKey({ key: spki, format: "der", type: "spki" });

// 6. Ed25519 over UTF-8 canonical JSON of the WHOLE receipt.
const bytes = Buffer.from(toCanonicalJson(receipt), "utf8");
const receiptOk = verify(null, bytes, publicKey, Buffer.from(signature, "base64url"));
if (!receiptOk) throw new Error("receipt signature invalid");

// 7. Bind the receipt to the CER you hold.
if (receipt.certificateHash !== cer.certificateHash) throw new Error("hash mismatch");
if (receipt.protocolVersion !== cer.version) throw new Error("version mismatch");
```

A valid receipt signature proves that the resolved Node key signed these receipt facts and bound the certificate hash to a Node-issued time and runtime descriptor. It does not prove state preimages, execution truth, producer authentication, completeness, public publication or independent wall-clock authority. Only a separately verified RFC 3161 token adds external time evidence.

## Verification-envelope signature verification

Descriptor fields: `algorithm` (`Ed25519`), `attestation`, `canonicalization` (`jcs`), `envelopeType` (`nexart.verification.envelope.v2`), `kid`, `scope` (`whitelist`), `signedFields`, `specVersion` (`v0.16.1`, an envelope specification label, not the Node version).

| CER version | attestation fields | bundle fields |
| --- | --- | --- |
| "0.1" | attestationId, attestedAt, kid, nodeRuntimeHash, protocolVersion | bundleType, version, createdAt, profile, evidence, protectedSet, then context, contextSummary if present |
| "0.2" | Same five | Same six, then identity, confidentiality if present, in that order |

The signed message is the canonical document of exactly this object:

```
{
  "attestation": {
    "attestationId": "<UUID>",
    "attestedAt": "<RFC3339>",
    "kid": "<KID>",
    "nodeRuntimeHash": "sha256:<HEX>",
    "protocolVersion": "0.1"
  },
  "bundle": {
    "bundleType": "cer.governed.execution.v1",
    "version": "0.1",
    "createdAt": "<RFC3339>",
    "profile": "<COPY THE COMPLETE PROFILE OBJECT>",
    "evidence": "<COPY THE COMPLETE EVIDENCE OBJECT>",
    "protectedSet": "<COPY THE COMPLETE PROTECTED-SET OBJECT>"
  }
}
```

The last three slots are the original objects, not JSON-encoded strings. The wrapper has only `attestation` and `bundle`. `certificateHash`, `meta`, `receipt`, `signature`, the rendered envelope and unknown extras are excluded.

MUST NOTVerify the signature over `JCS(verificationEnvelope)`. That is the older envelope-v1 helper, not the governed `/api/attest` path, and produces invalid results.

- Retain the submitted sealed CER. `/api/attest` does not return it.
- Read `verificationEnvelope.attestation` and its `kid`.
- Validate family and version and use your own expected whitelist. Do not accept the returned `signedFields` as authority.
- Reconstruct `{ attestation, bundle }` from the five attestation fields and the required plus actually-present optional bundle fields.
- Canonicalize with the same rules and verify `verificationEnvelopeSignature` with the key for that `kid` under your lifecycle policy.
- Verify the CER with Governed SDK 0.4.2 and verify the receipt. The projection omits `certificateHash`, so the hash binding needs those checks.

verify-envelope.ts

```
// Reuses publicKey resolution from the receipt procedure, keyed by
// verificationEnvelope.attestation.kid.
const env = attestResponse.verificationEnvelope;

// 3. Use YOUR expected whitelist for this family/version, not env.signedFields.
const ATTESTATION_FIELDS = ["attestationId", "attestedAt", "kid", "nodeRuntimeHash", "protocolVersion"];
const BUNDLE_REQUIRED = ["bundleType", "version", "createdAt", "profile", "evidence", "protectedSet"];
const BUNDLE_OPTIONAL = cer.version === "0.2" ? ["identity", "confidentiality"] : ["context", "contextSummary"];

const pick = (obj, keys) => Object.fromEntries(keys.filter((k) => k in obj).map((k) => [k, obj[k]]));

// 4. Reconstruct exactly { attestation, bundle }. No other wrapper keys.
const signable = {
  attestation: pick(env.attestation, ATTESTATION_FIELDS),
  bundle: pick(cer, [...BUNDLE_REQUIRED, ...BUNDLE_OPTIONAL]),
};

// 5. Ed25519 over UTF-8 canonical JSON of the projection, NOT of the envelope descriptor.
const envelopeOk = verify(
  null,
  Buffer.from(toCanonicalJson(signable), "utf8"),
  publicKey,
  Buffer.from(attestResponse.verificationEnvelopeSignature, "base64url"),
);
if (!envelopeOk) throw new Error("envelope signature invalid");
// 6. certificateHash is NOT in the projection: also run verifyGovernedExecution(cer)
//    and the receipt procedure to verify the hash binding.
```

For Node-side checking, attach the descriptor and signature at `bundle.meta.verificationEnvelope` and `bundle.meta.verificationEnvelopeSignature`, then send `{ "bundle": bundle }` to `/v1/cer/verify`. Outer-level placement is not equivalent. The route does not verify the receipt signature for you. Node-side checking is not independent verification.

A valid envelope signature proves the resolved Node key signed this family projection and five attestation facts. It does not authenticate the producer, validate state preimages, guarantee complete capture or certify public visibility.

## governedVerification fields

| Field | Values | Also in stateless checks |
| --- | --- | --- |
| certificateIntegrity | valid \| invalid \| not-performed | Yes |
| cageSchemaValidity | valid \| invalid \| not-performed | Yes |
| causalGraphValidity | valid \| invalid \| not-performed | Yes |
| topologyValidation | valid \| invalid \| not-supplied | Yes |
| resourceSafety | valid \| invalid \| not-performed | Yes |
| stateHashVerification | not-performed | No |
| producerAuthentication | not-performed | No |
| completeness | not-claimed | No |
| warnings | string array, e.g. TERMINAL_PATH_UNKNOWN | No |

The object has no `ok`, `code`, `errors` or `details`. SDK codes: `OK`, `SCHEMA_ERROR`, `CAUSAL_ERROR`, `TOPOLOGY_ERROR`, `CERTIFICATE_HASH_MISMATCH`, `CANONICALIZATION_ERROR`, `RESOURCE_ERROR`. The Node may add `INVALID_STRUCTURE` and a defensive `GOVERNED_VERIFICATION_FAILED`. Missing SDK fields default to not-performed, not-supplied or not-claimed, never to success.

## Stateless verification: POST /v1/cer/verify

```
POST /v1/cer/verify
Content-Type: application/json

{ "bundle": <complete sealed CER object> }
```

- No Bearer header. Public, rate-limited (source default 60/min) and structure-guarded.
- HTTP 200 for both `verified` and `failed` on a recognized family. HTTP 400 for a missing bundle or unsupported family.
- `computedCertificateHash` equals the submitted hash only on SDK success, otherwise `null`.
- Does not persist, mint a receipt or envelope, or publish. It may read stored TSA evidence and key metadata.
- `checks.signature` is `pass`, `fail` or `skipped`.

Verified: plain v0.1 CER, no topology, signature or TSA

```
{
  "status": "verified",
  "reason": "All checks passed",
  "reasonCode": "OK",
  "checks": {
    "certificateIntegrity": "valid",
    "cageSchemaValidity": "valid",
    "causalGraphValidity": "valid",
    "topologyValidation": "not-supplied",
    "resourceSafety": "valid",
    "signature": "skipped"
  },
  "certificateHash": "sha256:<CERTIFICATE_HEX>",
  "submittedCertificateHash": "sha256:<CERTIFICATE_HEX>",
  "computedCertificateHash": "sha256:<CERTIFICATE_HEX>",
  "keyUsed": null,
  "keySource": null,
  "anchorStatus": { "checked": false, "valid": true },
  "timestampStatus": { "checked": false, "valid": true },
  "timestampVerdict": "NOT_PRESENT",
  "timestampType": null,
  "timestampProvider": null,
  "timestamp": { "type": "node-issued", "provider": null, "status": "untrusted" },
  "context": null,
  "classification": "VERIFIED",
  "assurance": null,
  "relaxations": [],
  "verifiedUnder": null,
  "code": "OK",
  "classificationReason": "All checks passed",
  "contentMode": null,
  "verifiedAt": "<RFC3339_VERIFICATION_TIME>",
  "node": "<NODE_ID>",
  "protocolVersion": "0.1",
  "governedVerification": {
    "certificateIntegrity": "valid",
    "cageSchemaValidity": "valid",
    "causalGraphValidity": "valid",
    "topologyValidation": "not-supplied",
    "resourceSafety": "valid",
    "stateHashVerification": "not-performed",
    "producerAuthentication": "not-performed",
    "completeness": "not-claimed",
    "warnings": []
  }
}
```

Failed example (abridged; unlisted fields match the verified shape). The malformed `stateHash` was inserted before sealing a synthetic test fixture so that integrity stays valid. This describes building a test input, not a reseal workflow.

HTTP 200, status failed

```
{
  "status": "failed",
  "reason": "bundle.steps[1].stateHash: lowercase 64-character hexadecimal required",
  "reasonCode": "SCHEMA_ERROR",
  "checks": {
    "certificateIntegrity": "valid",
    "cageSchemaValidity": "invalid",
    "causalGraphValidity": "valid",
    "topologyValidation": "not-supplied",
    "resourceSafety": "valid",
    "signature": "skipped"
  },
  "certificateHash": "sha256:<CERTIFICATE_HEX>",
  "submittedCertificateHash": "sha256:<CERTIFICATE_HEX>",
  "computedCertificateHash": null,
  "classification": "FAILED",
  "code": "SCHEMA_ERROR",
  "node": "<NODE_ID>",
  "protocolVersion": "0.1",
  "governedVerification": {
    "certificateIntegrity": "valid",
    "cageSchemaValidity": "invalid",
    "causalGraphValidity": "valid",
    "topologyValidation": "not-supplied",
    "resourceSafety": "valid",
    "stateHashVerification": "not-performed",
    "producerAuthentication": "not-performed",
    "completeness": "not-claimed",
    "warnings": []
  }
}
```

## Persistence versus publication

- A successful attestation is persisted in the Node&#x27;s local `cer_proofs` store with the cleaned CER, receipt, signatures, envelope and `governedVerification`. `execution_id` is `bundleId`; `sdk_version` is null.
- Default visibility is `hidden`. Hidden, private, deleted and missing records all return indistinguishable 404s on public reads.
- Governed records are never forwarded to external CER ingest, even when local visibility is public.
- An authenticated owner (Bearer API key that owns the record) may call `POST /api/proofs/:certificateHash/unhide` (local `hidden` to `public`) or `POST /api/proofs/:certificateHash/visibility` with `{ "visibility": "public" }` or `"private"`. Visibility is Node record state (`visibility_state`), not a CER field, so it is outside the protected hash projection. The transition creates no new CER, receipt or signature and the record resolves locally under the same certificate hash.
- Node 0.30.1 supports an authenticated visibility transition that does not change the protected certificate hash. This source audit does not establish the external verifier&#x27;s storage or lookup implementation.

`verificationUrl` is formatted, not checked. Because the `verify.nexart.io` backend is outside the audited repository, a successful attestation, even with public local visibility, is not a guarantee that the URL resolves.

## Route status

| Route | Status | Use |
| --- | --- | --- |
| POST /api/attest | Current | Authenticated governed attestation |
| POST /v1/cer/verify | Current | Public, non-persisting verification |
| GET /.well-known/nexart-node.json | Current | Key discovery |
| GET /v1/resolve/cer/:certificateHash | Current | Exact-hash public resolution of local records |
| GET /v1/cer/public | Current | Public read by certificate_hash or execution_id |
| GET /api/public-cer-proof | Current, older surface | Public proof read; not an attestation alias |
| POST /api/proofs/:certificateHash/unhide | Current | Owner hidden to public |
| POST /api/proofs/:certificateHash/visibility | Current | Owner public/private management |
| POST /verify | Current, legacy-shaped | Authenticated render/snapshot verification; not governed |
| POST /v1/cage/bundles | Current, separate contract | Native CAGE bundle registration |
| POST /api/stamp | Governed unsupported | Rejects governed CERs |
| POST /api/stamp-from-execution | Cached replay | Not a governed reseal |
| /certifyDecision | Nonexistent | SDK helper name, not an HTTP route |
| /verify/{hash} | Nonexistent | Not POST /verify or the external /c/ page |
| /v1/attestation/cage-governance | Nonexistent | No alias |

Absence statements come from route registration in source, not production probing.

## Key discovery

GET /.well-known/nexart-node.json (default single active key)

```
{
  "nodeId": "<NODE_ID>",
  "operator": "<OPERATOR>",
  "activeKid": "<ACTIVE_KID>",
  "keys": [
    {
      "kid": "<KID>",
      "alg": "Ed25519",
      "algorithm": "Ed25519",
      "use": "sig",
      "status": "active",
      "publicKey": "<BASE64URL_32_BYTE_PUBLIC_KEY>",
      "publicKeyJwk": { "kty": "OKP", "crv": "Ed25519", "x": "<BASE64URL_PUBLIC_KEY>" },
      "publicKeySpkiB64": "<BASE64_PUBLIC_SPKI>",
      "createdAt": "<RFC3339>",
      "revoked": false,
      "notBefore": "<RFC3339>",
      "validFrom": "<RFC3339>",
      "validTo": null
    }
  ]
}
```

Additional and deprecated keys may appear with `deprecatedAt`, `notAfter` and `validTo`. Status is `active`, `deprecated` or `revoked`. Always select by the signature&#x27;s `kid`.
